diff options
Diffstat (limited to 'java/src/com/android/inputmethod/research/LogUnit.java')
-rw-r--r-- | java/src/com/android/inputmethod/research/LogUnit.java | 220 |
1 files changed, 202 insertions, 18 deletions
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java index d8b3a29ff..27c4027de 100644 --- a/java/src/com/android/inputmethod/research/LogUnit.java +++ b/java/src/com/android/inputmethod/research/LogUnit.java @@ -16,9 +16,23 @@ package com.android.inputmethod.research; -import com.android.inputmethod.latin.CollectionUtils; +import android.content.SharedPreferences; +import android.util.JsonWriter; +import android.util.Log; +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.Utils; +import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.research.ResearchLogger.LogStatement; + +import java.io.IOException; +import java.io.StringWriter; import java.util.ArrayList; +import java.util.List; +import java.util.Map; /** * A group of log statements related to each other. @@ -35,28 +49,163 @@ 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 static final String TAG = LogUnit.class.getSimpleName(); + private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG; + private final ArrayList<LogStatement> mLogStatementList; + private final ArrayList<Object[]> mValuesList; + // Assume that mTimeList is sorted in increasing order. Do not insert null values into + // mTimeList. + private final ArrayList<Long> mTimeList; private String mWord; - private boolean mContainsDigit; + private boolean mMayContainDigit; + private boolean mIsPartOfMegaword; + + public LogUnit() { + mLogStatementList = new ArrayList<LogStatement>(); + mValuesList = new ArrayList<Object[]>(); + mTimeList = new ArrayList<Long>(); + mIsPartOfMegaword = false; + } - public void addLogStatement(final String[] keys, final Object[] values, - final Boolean isPotentiallyPrivate) { - mKeysList.add(keys); + private LogUnit(final ArrayList<LogStatement> logStatementList, + final ArrayList<Object[]> valuesList, + final ArrayList<Long> timeList, + final boolean isPartOfMegaword) { + mLogStatementList = logStatementList; + mValuesList = valuesList; + mTimeList = timeList; + mIsPartOfMegaword = isPartOfMegaword; + } + + private static final Object[] NULL_VALUES = new Object[0]; + /** + * 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 LogStatement logStatement, final long time, + Object... values) { + if (values == null) { + values = NULL_VALUES; + } + mLogStatementList.add(logStatement); mValuesList.add(values); - mIsPotentiallyPrivate.add(isPotentiallyPrivate); + mTimeList.add(time); } - public void publishTo(final ResearchLog researchLog, final boolean isIncludingPrivateData) { - final int size = mKeysList.size(); + /** + * Publish the contents of this LogUnit to researchLog. + */ + public synchronized void publishTo(final ResearchLog researchLog, + final boolean isIncludingPrivateData) { + // Prepare debugging output if necessary + final StringWriter debugStringWriter; + final JsonWriter debugJsonWriter; + if (DEBUG) { + debugStringWriter = new StringWriter(); + debugJsonWriter = new JsonWriter(debugStringWriter); + debugJsonWriter.setIndent(" "); + try { + debugJsonWriter.beginArray(); + } catch (IOException e) { + Log.e(TAG, "Could not open array in JsonWriter", e); + } + } else { + debugStringWriter = null; + debugJsonWriter = null; + } + final int size = mLogStatementList.size(); + // Write out any logStatement that passes the privacy filter. for (int i = 0; i < size; i++) { - if (!mIsPotentiallyPrivate.get(i) || isIncludingPrivateData) { - researchLog.outputEvent(mKeysList.get(i), mValuesList.get(i)); + final LogStatement logStatement = mLogStatementList.get(i); + if (!isIncludingPrivateData && logStatement.mIsPotentiallyPrivate) { + continue; + } + if (mIsPartOfMegaword && logStatement.mIsPotentiallyRevealing) { + continue; + } + // Only retrieve the jsonWriter if we need to. If we don't get this far, then + // researchLog.getValidJsonWriter() will not open the file for writing. + final JsonWriter jsonWriter = researchLog.getValidJsonWriterLocked(); + outputLogStatementToLocked(jsonWriter, mLogStatementList.get(i), mValuesList.get(i), + mTimeList.get(i)); + if (DEBUG) { + outputLogStatementToLocked(debugJsonWriter, mLogStatementList.get(i), + mValuesList.get(i), mTimeList.get(i)); + } + } + if (DEBUG) { + try { + debugJsonWriter.endArray(); + debugJsonWriter.flush(); + } catch (IOException e) { + Log.e(TAG, "Could not close array in JsonWriter", e); + } + final String bigString = debugStringWriter.getBuffer().toString(); + final String[] lines = bigString.split("\n"); + for (String line : lines) { + Log.d(TAG, line); } } } + private static final String CURRENT_TIME_KEY = "_ct"; + private static final String UPTIME_KEY = "_ut"; + private static final String EVENT_TYPE_KEY = "_ty"; + + /** + * 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.mKeys.length != values.length) { + Log.d(TAG, "Key and Value list sizes do not match. " + logStatement.mName); + } + } + try { + jsonWriter.beginObject(); + jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis()); + jsonWriter.name(UPTIME_KEY).value(time); + jsonWriter.name(EVENT_TYPE_KEY).value(logStatement.mName); + final String[] keys = logStatement.mKeys; + 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 == null) { + jsonWriter.nullValue(); + } else { + Log.w(TAG, "Unrecognized type to be logged: " + + (value == null ? "<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; + } + public void setWord(String word) { mWord = word; } @@ -69,15 +218,50 @@ 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(); + return mLogStatementList.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 List<LogStatement> laterLogStatements = + mLogStatementList.subList(index, length); + final List<Object[]> laterValues = mValuesList.subList(index, length); + final List<Long> laterTimes = mTimeList.subList(index, length); + + // Create the LogUnit containing the later logStatements and associated data. + final LogUnit newLogUnit = new LogUnit( + new ArrayList<LogStatement>(laterLogStatements), + new ArrayList<Object[]>(laterValues), + new ArrayList<Long>(laterTimes), + true /* isPartOfMegaword */); + newLogUnit.mWord = null; + newLogUnit.mMayContainDigit = mMayContainDigit; + + // Purge the logStatements and associated data from this LogUnit. + laterLogStatements.clear(); + laterValues.clear(); + laterTimes.clear(); + mIsPartOfMegaword = true; + + return newLogUnit; + } + } + return new LogUnit(); } } |