diff --git a/java/src/com/android/inputmethod/research/LogStatement.java b/java/src/com/android/inputmethod/research/LogStatement.java index 1d83e1a86..059146ae6 100644 --- a/java/src/com/android/inputmethod/research/LogStatement.java +++ b/java/src/com/android/inputmethod/research/LogStatement.java @@ -16,6 +16,18 @@ package com.android.inputmethod.research; +import android.content.SharedPreferences; +import android.util.JsonWriter; +import android.util.Log; +import android.view.MotionEvent; +import android.view.inputmethod.CompletionInfo; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.latin.SuggestedWords; +import com.android.inputmethod.latin.define.ProductionFlag; + +import java.io.IOException; + /** * A template for typed information stored in the logs. * @@ -24,6 +36,9 @@ package com.android.inputmethod.research; * actual values are stored separately. */ class LogStatement { + private static final String TAG = LogStatement.class.getSimpleName(); + private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG; + // Constants for particular statements public static final String TYPE_POINTER_TRACKER_CALL_LISTENER_ON_CODE_INPUT = "PointerTrackerCallListenerOnCodeInput"; @@ -36,6 +51,11 @@ class LogStatement { public static final String TYPE_MOTION_EVENT = "MotionEvent"; public static final String KEY_IS_LOGGING_RELATED = "isLoggingRelated"; + // Keys for internal key/value pairs + private static final String CURRENT_TIME_KEY = "_ct"; + private static final String UPTIME_KEY = "_ut"; + private static final String EVENT_TYPE_KEY = "_ty"; + // Name specifying the LogStatement type. private final String mType; @@ -142,4 +162,61 @@ class LogStatement { } return false; } + + /** + * Write the contents out through jsonWriter. + * + * Note that this method is not thread safe for the same jsonWriter. Callers must ensure + * thread safety. + */ + public boolean outputToLocked(final JsonWriter jsonWriter, final Long time, + final Object... values) { + if (DEBUG) { + if (mKeys.length != values.length) { + Log.d(TAG, "Key and Value list sizes do not match. " + mType); + } + } + try { + jsonWriter.beginObject(); + jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis()); + jsonWriter.name(UPTIME_KEY).value(time); + jsonWriter.name(EVENT_TYPE_KEY).value(mType); + final int length = values.length; + for (int i = 0; i < length; i++) { + jsonWriter.name(mKeys[i]); + final Object value = values[i]; + if (value instanceof CharSequence) { + jsonWriter.value(value.toString()); + } else if (value instanceof Number) { + jsonWriter.value((Number) value); + } else if (value instanceof Boolean) { + jsonWriter.value((Boolean) value); + } else if (value instanceof CompletionInfo[]) { + JsonUtils.writeJson((CompletionInfo[]) value, jsonWriter); + } else if (value instanceof SharedPreferences) { + JsonUtils.writeJson((SharedPreferences) value, jsonWriter); + } else if (value instanceof Key[]) { + JsonUtils.writeJson((Key[]) value, jsonWriter); + } else if (value instanceof SuggestedWords) { + JsonUtils.writeJson((SuggestedWords) value, jsonWriter); + } else if (value instanceof MotionEvent) { + JsonUtils.writeJson((MotionEvent) value, jsonWriter); + } else if (value == null) { + jsonWriter.nullValue(); + } else { + if (DEBUG) { + Log.w(TAG, "Unrecognized type to be logged: " + + (value == null ? "" : value.getClass().getName())); + } + jsonWriter.nullValue(); + } + } + jsonWriter.endObject(); + } catch (IOException e) { + e.printStackTrace(); + Log.w(TAG, "Error in JsonWriter; skipping LogStatement"); + return false; + } + return true; + } } diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java index 2e732fc6c..a584a3af6 100644 --- a/java/src/com/android/inputmethod/research/LogUnit.java +++ b/java/src/com/android/inputmethod/research/LogUnit.java @@ -17,13 +17,11 @@ package com.android.inputmethod.research; import android.content.SharedPreferences; +import android.os.SystemClock; import android.text.TextUtils; import android.util.JsonWriter; import android.util.Log; -import android.view.MotionEvent; -import android.view.inputmethod.CompletionInfo; -import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.define.ProductionFlag; @@ -153,11 +151,10 @@ import java.util.List; jsonWriter = researchLog.getValidJsonWriterLocked(); outputLogUnitStart(jsonWriter, canIncludePrivateData); } - outputLogStatementToLocked(jsonWriter, mLogStatementList.get(i), mValuesList.get(i), - mTimeList.get(i)); + logStatement.outputToLocked(jsonWriter, mTimeList.get(i), mValuesList.get(i)); if (DEBUG) { - outputLogStatementToLocked(debugJsonWriter, mLogStatementList.get(i), - mValuesList.get(i), mTimeList.get(i)); + logStatement.outputToLocked(debugJsonWriter, mTimeList.get(i), + mValuesList.get(i)); } } if (jsonWriter != null) { @@ -180,97 +177,34 @@ import java.util.List; } } - 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 String WORD_KEY = "_wo"; private static final String CORRECTION_TYPE_KEY = "_corType"; private static final String LOG_UNIT_BEGIN_KEY = "logUnitStart"; private static final String LOG_UNIT_END_KEY = "logUnitEnd"; + final LogStatement LOGSTATEMENT_LOG_UNIT_BEGIN_WITH_PRIVATE_DATA = + new LogStatement(LOG_UNIT_BEGIN_KEY, false /* isPotentiallyPrivate */, + false /* isPotentiallyRevealing */, WORD_KEY, CORRECTION_TYPE_KEY); + final LogStatement LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA = + new LogStatement(LOG_UNIT_BEGIN_KEY, false /* isPotentiallyPrivate */, + false /* isPotentiallyRevealing */); private void outputLogUnitStart(final JsonWriter jsonWriter, final boolean canIncludePrivateData) { - try { - jsonWriter.beginObject(); - jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis()); - if (canIncludePrivateData) { - jsonWriter.name(WORD_KEY).value(getWord()); - jsonWriter.name(CORRECTION_TYPE_KEY).value(getCorrectionType()); - } - jsonWriter.name(EVENT_TYPE_KEY).value(LOG_UNIT_BEGIN_KEY); - jsonWriter.endObject(); - } catch (IOException e) { - e.printStackTrace(); - Log.w(TAG, "Error in JsonWriter; cannot write LogUnitStart"); + final LogStatement logStatement; + if (canIncludePrivateData) { + LOGSTATEMENT_LOG_UNIT_BEGIN_WITH_PRIVATE_DATA.outputToLocked(jsonWriter, + SystemClock.uptimeMillis(), getWord(), getCorrectionType()); + } else { + LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA.outputToLocked(jsonWriter, + SystemClock.uptimeMillis()); } } + final LogStatement LOGSTATEMENT_LOG_UNIT_END = + new LogStatement(LOG_UNIT_END_KEY, false /* isPotentiallyPrivate */, + false /* isPotentiallyRevealing */); private void outputLogUnitStop(final JsonWriter jsonWriter) { - try { - jsonWriter.beginObject(); - jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis()); - jsonWriter.name(EVENT_TYPE_KEY).value(LOG_UNIT_END_KEY); - jsonWriter.endObject(); - } catch (IOException e) { - e.printStackTrace(); - Log.w(TAG, "Error in JsonWriter; cannot write LogUnitStop"); - } - } - - /** - * Write the logStatement and its contents out through jsonWriter. - * - * Note that this method is not thread safe for the same jsonWriter. Callers must ensure - * thread safety. - */ - private boolean outputLogStatementToLocked(final JsonWriter jsonWriter, - final LogStatement logStatement, final Object[] values, final Long time) { - if (DEBUG) { - if (logStatement.getKeys().length != values.length) { - Log.d(TAG, "Key and Value list sizes do not match. " + logStatement.getType()); - } - } - try { - jsonWriter.beginObject(); - jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis()); - jsonWriter.name(UPTIME_KEY).value(time); - jsonWriter.name(EVENT_TYPE_KEY).value(logStatement.getType()); - final String[] keys = logStatement.getKeys(); - final int length = values.length; - for (int i = 0; i < length; i++) { - jsonWriter.name(keys[i]); - final Object value = values[i]; - if (value instanceof CharSequence) { - jsonWriter.value(value.toString()); - } else if (value instanceof Number) { - jsonWriter.value((Number) value); - } else if (value instanceof Boolean) { - jsonWriter.value((Boolean) value); - } else if (value instanceof CompletionInfo[]) { - JsonUtils.writeJson((CompletionInfo[]) value, jsonWriter); - } else if (value instanceof SharedPreferences) { - JsonUtils.writeJson((SharedPreferences) value, jsonWriter); - } else if (value instanceof Key[]) { - JsonUtils.writeJson((Key[]) value, jsonWriter); - } else if (value instanceof SuggestedWords) { - JsonUtils.writeJson((SuggestedWords) value, jsonWriter); - } else if (value instanceof MotionEvent) { - JsonUtils.writeJson((MotionEvent) value, jsonWriter); - } else if (value == null) { - jsonWriter.nullValue(); - } else { - Log.w(TAG, "Unrecognized type to be logged: " - + (value == null ? "" : value.getClass().getName())); - jsonWriter.nullValue(); - } - } - jsonWriter.endObject(); - } catch (IOException e) { - e.printStackTrace(); - Log.w(TAG, "Error in JsonWriter; skipping LogStatement"); - return false; - } - return true; + LOGSTATEMENT_LOG_UNIT_END.outputToLocked(jsonWriter, SystemClock.uptimeMillis()); } /** diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index 8fc62ea7b..364ab2da2 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -1003,15 +1003,23 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } } + /** + * Publish all the logUnits in the logBuffer, without doing any privacy filtering. + */ /* package for test */ void publishLogBuffer(final LogBuffer logBuffer, - final ResearchLog researchLog, final boolean isIncludingPrivateData) { - publishLogUnits(logBuffer.getLogUnits(), researchLog, isIncludingPrivateData); + final ResearchLog researchLog, final boolean canIncludePrivateData) { + publishLogUnits(logBuffer.getLogUnits(), researchLog, canIncludePrivateData); } private static final LogStatement LOGSTATEMENT_LOG_SEGMENT_OPENING = new LogStatement("logSegmentStart", false, false, "isIncludingPrivateData"); private static final LogStatement LOGSTATEMENT_LOG_SEGMENT_CLOSING = new LogStatement("logSegmentEnd", false, false); + /** + * Publish all LogUnits in a list. + * + * Any privacy checks should be performed before calling this method. + */ /* package for test */ void publishLogUnits(final List logUnits, final ResearchLog researchLog, final boolean canIncludePrivateData) { final LogUnit openingLogUnit = new LogUnit(); @@ -1392,7 +1400,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final int index, final String suggestion, final boolean isBatchMode) { final ResearchLogger researchLogger = getInstance(); if (!replacedWord.equals(suggestion.toString())) { - // The user choose something other than what was already there. + // The user chose something other than what was already there. researchLogger.setCurrentLogUnitContainsCorrection(); researchLogger.setCurrentLogUnitCorrectionType(LogUnit.CORRECTIONTYPE_TYPO); }