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