diff options
Diffstat (limited to 'java/src/com/android/inputmethod/research')
5 files changed, 140 insertions, 73 deletions
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java index d8b3a29ff..0aec80a40 100644 --- a/java/src/com/android/inputmethod/research/LogUnit.java +++ b/java/src/com/android/inputmethod/research/LogUnit.java @@ -18,7 +18,7 @@ package com.android.inputmethod.research; import com.android.inputmethod.latin.CollectionUtils; -import java.util.ArrayList; +import java.util.List; /** * A group of log statements related to each other. @@ -35,16 +35,39 @@ import java.util.ArrayList; * been published recently, or whether the LogUnit contains numbers, etc. */ /* package */ class LogUnit { - private final ArrayList<String[]> mKeysList = CollectionUtils.newArrayList(); - private final ArrayList<Object[]> mValuesList = CollectionUtils.newArrayList(); - private final ArrayList<Boolean> mIsPotentiallyPrivate = CollectionUtils.newArrayList(); + private final List<String[]> mKeysList; + private final List<Object[]> mValuesList; + // Assume that mTimeList is sorted in increasing order. Do not insert null values into + // mTimeList. + private final List<Long> mTimeList; + private final List<Boolean> mIsPotentiallyPrivate; private String mWord; - private boolean mContainsDigit; + private boolean mMayContainDigit; + public LogUnit() { + mKeysList = CollectionUtils.newArrayList(); + mValuesList = CollectionUtils.newArrayList(); + mTimeList = CollectionUtils.newArrayList(); + mIsPotentiallyPrivate = CollectionUtils.newArrayList(); + } + + private LogUnit(final List<String[]> keysList, final List<Object[]> valuesList, + final List<Long> timeList, final List<Boolean> isPotentiallyPrivate) { + mKeysList = keysList; + mValuesList = valuesList; + mTimeList = timeList; + mIsPotentiallyPrivate = isPotentiallyPrivate; + } + + /** + * Adds a new log statement. The time parameter in successive calls to this method must be + * monotonically increasing, or splitByTime() will not work. + */ public void addLogStatement(final String[] keys, final Object[] values, - final Boolean isPotentiallyPrivate) { + final long time, final boolean isPotentiallyPrivate) { mKeysList.add(keys); mValuesList.add(values); + mTimeList.add(time); mIsPotentiallyPrivate.add(isPotentiallyPrivate); } @@ -52,7 +75,7 @@ import java.util.ArrayList; final int size = mKeysList.size(); for (int i = 0; i < size; i++) { if (!mIsPotentiallyPrivate.get(i) || isIncludingPrivateData) { - researchLog.outputEvent(mKeysList.get(i), mValuesList.get(i)); + researchLog.outputEvent(mKeysList.get(i), mValuesList.get(i), mTimeList.get(i)); } } } @@ -69,15 +92,37 @@ import java.util.ArrayList; return mWord != null; } - public void setContainsDigit() { - mContainsDigit = true; + public void setMayContainDigit() { + mMayContainDigit = true; } - public boolean hasDigit() { - return mContainsDigit; + public boolean mayContainDigit() { + return mMayContainDigit; } public boolean isEmpty() { return mKeysList.isEmpty(); } + + /** + * Split this logUnit, with all events before maxTime staying in the current logUnit, and all + * events after maxTime going into a new LogUnit that is returned. + */ + public LogUnit splitByTime(final long maxTime) { + // Assume that mTimeList is in sorted order. + final int length = mTimeList.size(); + for (int index = 0; index < length; index++) { + if (mTimeList.get(index) >= maxTime) { + final LogUnit newLogUnit = new LogUnit( + mKeysList.subList(index, length), + mValuesList.subList(index, length), + mTimeList.subList(index, length), + mIsPotentiallyPrivate.subList(index, length)); + newLogUnit.mWord = null; + newLogUnit.mMayContainDigit = mMayContainDigit; + return newLogUnit; + } + } + return new LogUnit(); + } } diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java index 745768d35..f665e5906 100644 --- a/java/src/com/android/inputmethod/research/MainLogBuffer.java +++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java @@ -16,16 +16,22 @@ package com.android.inputmethod.research; +import android.util.Log; + import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.Suggest; import java.util.Random; public class MainLogBuffer extends LogBuffer { + private static final String TAG = MainLogBuffer.class.getSimpleName(); + // For privacy reasons, be sure to set to "false" for production code. + private static final boolean DEBUG = false; + // The size of the n-grams logged. E.g. N_GRAM_SIZE = 2 means to sample bigrams. private static final int N_GRAM_SIZE = 2; // The number of words between n-grams to omit from the log. - private static final int DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES = 18; + private static final int DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES = DEBUG ? 2 : 18; private final ResearchLog mResearchLog; private Suggest mSuggest; @@ -61,6 +67,9 @@ public class MainLogBuffer extends LogBuffer { mWordsUntilSafeToSample--; } } + if (DEBUG) { + Log.d(TAG, "shiftedIn " + (newLogUnit.hasWord() ? newLogUnit.getWord() : "")); + } } public void resetWordCounter() { @@ -104,7 +113,7 @@ public class MainLogBuffer extends LogBuffer { final String word = logUnit.getWord(); if (word == null) { // Digits outside words are a privacy threat. - if (logUnit.hasDigit()) { + if (logUnit.mayContainDigit()) { return false; } } else { diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java index 70c38e909..96dac55ac 100644 --- a/java/src/com/android/inputmethod/research/ResearchLog.java +++ b/java/src/com/android/inputmethod/research/ResearchLog.java @@ -207,7 +207,7 @@ public class ResearchLog { private static final String UPTIME_KEY = "_ut"; private static final String EVENT_TYPE_KEY = "_ty"; - void outputEvent(final String[] keys, final Object[] values) { + void outputEvent(final String[] keys, final Object[] values, final long time) { // Not thread safe. if (keys.length == 0) { return; @@ -225,7 +225,7 @@ public class ResearchLog { } mJsonWriter.beginObject(); mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis()); - mJsonWriter.name(UPTIME_KEY).value(SystemClock.uptimeMillis()); + mJsonWriter.name(UPTIME_KEY).value(time); mJsonWriter.name(EVENT_TYPE_KEY).value(keys[0]); final int length = values.length; for (int i = 0; i < length; i++) { diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index 763fd6e00..2657da285 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -57,9 +57,9 @@ import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.keyboard.MainKeyboardView; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Dictionary; +import com.android.inputmethod.latin.InputTypeUtils; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputConnection; @@ -143,7 +143,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private final Statistics mStatistics; private Intent mUploadIntent; - private PendingIntent mUploadPendingIntent; private LogUnit mCurrentLogUnit = new LogUnit(); @@ -188,7 +187,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang mInputMethodService = ims; mPrefs = prefs; mUploadIntent = new Intent(mInputMethodService, UploaderService.class); - mUploadPendingIntent = PendingIntent.getService(mInputMethodService, 0, mUploadIntent, 0); if (ProductionFlag.IS_EXPERIMENTAL) { scheduleUploadingService(mInputMethodService); @@ -379,7 +377,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang Log.d(TAG, "stop called"); } logStatistics(); - commitCurrentLogUnit(); + commitCurrentLogUnit(SystemClock.uptimeMillis()); if (mMainLogBuffer != null) { publishLogBuffer(mMainLogBuffer, mMainResearchLog, false /* isIncludingPrivateData */); @@ -427,21 +425,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } private long mResumeTime = 0L; - private void suspendLoggingUntil(long time) { - mIsLoggingSuspended = true; - mResumeTime = time; - requestIndicatorRedraw(); - } - - private void resumeLogging() { - mResumeTime = 0L; - updateSuspendedState(); - requestIndicatorRedraw(); - if (isAllowedToLog()) { - restart(); - } - } - private void updateSuspendedState() { final long time = System.currentTimeMillis(); if (time > mResumeTime) { @@ -547,7 +530,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang return; } if (includeHistory) { - commitCurrentLogUnit(); + commitCurrentLogUnit(SystemClock.uptimeMillis()); } else { mFeedbackLogBuffer.clear(); } @@ -556,7 +539,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang feedbackContents }; feedbackLogUnit.addLogStatement(EVENTKEYS_FEEDBACK, values, - false /* isPotentiallyPrivate */); + SystemClock.uptimeMillis(), false /* isPotentiallyPrivate */); mFeedbackLogBuffer.shiftIn(feedbackLogUnit); publishLogBuffer(mFeedbackLogBuffer, mFeedbackLog, true /* isIncludingPrivateData */); mFeedbackLog.close(new Runnable() { @@ -658,12 +641,13 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final Object[] values) { assert values.length + 1 == keys.length; if (isAllowedToLog()) { - mCurrentLogUnit.addLogStatement(keys, values, true /* isPotentiallyPrivate */); + final long time = SystemClock.uptimeMillis(); + mCurrentLogUnit.addLogStatement(keys, values, time, true /* isPotentiallyPrivate */); } } private void setCurrentLogUnitContainsDigitFlag() { - mCurrentLogUnit.setContainsDigit(); + mCurrentLogUnit.setMayContainDigit(); } /** @@ -681,15 +665,18 @@ 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 */); + final long time = SystemClock.uptimeMillis(); + mCurrentLogUnit.addLogStatement(keys, values, time, false /* isPotentiallyPrivate */); } } - /* package for test */ void commitCurrentLogUnit() { + /* package for test */ void commitCurrentLogUnit(final long maxTime) { if (DEBUG) { - Log.d(TAG, "commitCurrentLogUnit"); + Log.d(TAG, "commitCurrentLogUnit" + (mCurrentLogUnit.hasWord() ? + ": " + mCurrentLogUnit.getWord() : "")); } if (!mCurrentLogUnit.isEmpty()) { + final LogUnit newLogUnit = mCurrentLogUnit.splitByTime(maxTime); if (mMainLogBuffer != null) { mMainLogBuffer.shiftIn(mCurrentLogUnit); if (mMainLogBuffer.isSafeToLog() && mMainResearchLog != null) { @@ -701,37 +688,54 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang if (mFeedbackLogBuffer != null) { mFeedbackLogBuffer.shiftIn(mCurrentLogUnit); } - mCurrentLogUnit = new LogUnit(); + mCurrentLogUnit = newLogUnit; Log.d(TAG, "commitCurrentLogUnit"); } } + private static final String[] EVENTKEYS_LOG_SEGMENT_START = { + "logSegmentStart", "isIncludingPrivateData" + }; + private static final String[] EVENTKEYS_LOG_SEGMENT_END = { + "logSegmentEnd" + }; /* package for test */ void publishLogBuffer(final LogBuffer logBuffer, final ResearchLog researchLog, final boolean isIncludingPrivateData) { + final LogUnit openingLogUnit = new LogUnit(); + final Object[] values = { + isIncludingPrivateData + }; + openingLogUnit.addLogStatement(EVENTKEYS_LOG_SEGMENT_START, values, + SystemClock.uptimeMillis(), false /* isPotentiallyPrivate */); + researchLog.publish(openingLogUnit, true /* isIncludingPrivateData */); LogUnit logUnit; while ((logUnit = logBuffer.shiftOut()) != null) { researchLog.publish(logUnit, isIncludingPrivateData); } + final LogUnit closingLogUnit = new LogUnit(); + closingLogUnit.addLogStatement(EVENTKEYS_LOG_SEGMENT_END, EVENTKEYS_NULLVALUES, + SystemClock.uptimeMillis(), false /* isPotentiallyPrivate */); + researchLog.publish(closingLogUnit, true /* isIncludingPrivateData */); } - private boolean hasOnlyLetters(final String word) { + private boolean hasLetters(final String word) { final int length = word.length(); for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) { final int codePoint = word.codePointAt(i); - if (!Character.isLetter(codePoint)) { - return false; + if (Character.isLetter(codePoint)) { + return true; } } - return true; + return false; } - private void onWordComplete(final String word) { + private void onWordComplete(final String word, final long maxTime) { Log.d(TAG, "onWordComplete: " + word); - if (word != null && word.length() > 0 && hasOnlyLetters(word)) { + if (word != null && word.length() > 0 && hasLetters(word)) { mCurrentLogUnit.setWord(word); mStatistics.recordWordEntered(); } - commitCurrentLogUnit(); + commitCurrentLogUnit(maxTime); } private static int scrubDigitFromCodePoint(int codePoint) { @@ -791,8 +795,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo, final SharedPreferences prefs) { final ResearchLogger researchLogger = getInstance(); - researchLogger.start(); if (editorInfo != null) { + final boolean isPassword = InputTypeUtils.isPasswordInputType(editorInfo.inputType) + || InputTypeUtils.isVisiblePasswordInputType(editorInfo.inputType); + getInstance().setIsPasswordView(isPassword); + researchLogger.start(); final Context context = researchLogger.mInputMethodService; try { final PackageInfo packageInfo; @@ -818,10 +825,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang stop(); } - private static final String[] EVENTKEYS_USER_FEEDBACK = { - "UserFeedback", "FeedbackContents" - }; - private static final String[] EVENTKEYS_PREFS_CHANGED = { "PrefsChanged", "prefs" }; @@ -870,7 +873,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final long time = SystemClock.uptimeMillis(); final ResearchLogger researchLogger = getInstance(); final Object[] values = { - Keyboard.printableCode(scrubDigitFromCodePoint(code)), x, y + Constants.printableCode(scrubDigitFromCodePoint(code)), x, y }; researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values); if (Character.isDigit(code)) { @@ -903,18 +906,20 @@ 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); - ic.setSelection(savedSelectionStart, savedSelectionEnd); - ic.endBatchEdit(); - sLatinIMEExpectingUpdateSelection = true; final Object[] values = new Object[2]; if (OUTPUT_ENTIRE_BUFFER) { + // 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); + if (savedSelectionStart != -1 && savedSelectionEnd != -1) { + ic.setSelection(savedSelectionStart, savedSelectionEnd); + } + ic.endBatchEdit(); + sLatinIMEExpectingUpdateSelection = true; if (TextUtils.isEmpty(charSequence)) { values[0] = false; values[1] = ""; @@ -941,7 +946,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } final ResearchLogger researchLogger = getInstance(); researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONWINDOWHIDDEN, values); - researchLogger.commitCurrentLogUnit(); + researchLogger.commitCurrentLogUnit(SystemClock.uptimeMillis()); getInstance().stop(); } } @@ -1006,7 +1011,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang }; public static void latinIME_sendKeyCodePoint(final int code) { final Object[] values = { - Keyboard.printableCode(scrubDigitFromCodePoint(code)) + Constants.printableCode(scrubDigitFromCodePoint(code)) }; final ResearchLogger researchLogger = getInstance(); researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values); @@ -1059,7 +1064,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang keyboard.mOccupiedHeight, keyboard.mKeys }; - getInstance().setIsPasswordView(isPasswordView); getInstance().enqueueEvent(EVENTKEYS_MAINKEYBOARDVIEW_SETKEYBOARD, values); } } @@ -1092,7 +1096,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang if (key != null) { String outputText = key.getOutputText(); final Object[] values = { - Keyboard.printableCode(scrubDigitFromCodePoint(code)), outputText == null ? null + Constants.printableCode(scrubDigitFromCodePoint(code)), outputText == null ? null : scrubDigitsFromString(outputText.toString()), x, y, ignoreModifierKey, altersCode, key.isEnabled() }; @@ -1109,7 +1113,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final boolean withSliding, final boolean ignoreModifierKey) { if (key != null) { final Object[] values = { - Keyboard.printableCode(scrubDigitFromCodePoint(primaryCode)), withSliding, + Constants.printableCode(scrubDigitFromCodePoint(primaryCode)), withSliding, ignoreModifierKey, key.isEnabled() }; getInstance().enqueuePotentiallyPrivateEvent( @@ -1188,7 +1192,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final ResearchLogger researchLogger = getInstance(); researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_COMMITTEXT, values); - researchLogger.onWordComplete(scrubbedWord); + // TODO: Replace Long.MAX_VALUE with timestamp of last data to include + researchLogger.onWordComplete(scrubbedWord, Long.MAX_VALUE); } private static final String[] EVENTKEYS_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT = { diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java index eab465aa2..2065ab15e 100644 --- a/java/src/com/android/inputmethod/research/Statistics.java +++ b/java/src/com/android/inputmethod/research/Statistics.java @@ -16,9 +16,14 @@ package com.android.inputmethod.research; -import com.android.inputmethod.keyboard.Keyboard; +import android.util.Log; + +import com.android.inputmethod.latin.Constants; public class Statistics { + private static final String TAG = Statistics.class.getSimpleName(); + private static final boolean DEBUG = false; + // Number of characters entered during a typing session int mCharCount; // Number of letter characters entered during a typing session @@ -103,8 +108,11 @@ public class Statistics { } public void recordChar(int codePoint, long time) { + if (DEBUG) { + Log.d(TAG, "recordChar() called"); + } final long delta = time - mLastTapTime; - if (codePoint == Keyboard.CODE_DELETE) { + if (codePoint == Constants.CODE_DELETE) { mDeleteKeyCount++; if (delta < MIN_DELETION_INTERMISSION) { if (mIsLastKeyDeleteKey) { |