diff --git a/java/src/com/android/inputmethod/research/JsonUtils.java b/java/src/com/android/inputmethod/research/JsonUtils.java new file mode 100644 index 000000000..cb331d7f9 --- /dev/null +++ b/java/src/com/android/inputmethod/research/JsonUtils.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.research; + +import android.content.SharedPreferences; +import android.util.JsonWriter; +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 java.io.IOException; +import java.util.Map; + +/* package */ class JsonUtils { + private JsonUtils() { + // This utility class is not publicly instantiable. + } + + /* package */ static void writeJson(final CompletionInfo[] ci, final JsonWriter jsonWriter) + throws IOException { + jsonWriter.beginArray(); + for (int j = 0; j < ci.length; j++) { + jsonWriter.value(ci[j].toString()); + } + jsonWriter.endArray(); + } + + /* package */ static void writeJson(final SharedPreferences prefs, final JsonWriter jsonWriter) + throws IOException { + jsonWriter.beginObject(); + for (Map.Entry entry : prefs.getAll().entrySet()) { + jsonWriter.name(entry.getKey()); + final Object innerValue = entry.getValue(); + if (innerValue == null) { + jsonWriter.nullValue(); + } else if (innerValue instanceof Boolean) { + jsonWriter.value((Boolean) innerValue); + } else if (innerValue instanceof Number) { + jsonWriter.value((Number) innerValue); + } else { + jsonWriter.value(innerValue.toString()); + } + } + jsonWriter.endObject(); + } + + /* package */ static void writeJson(final Key[] keys, final JsonWriter jsonWriter) + throws IOException { + jsonWriter.beginArray(); + for (Key key : keys) { + writeJson(key, jsonWriter); + } + jsonWriter.endArray(); + } + + private static void writeJson(final Key key, final JsonWriter jsonWriter) throws IOException { + jsonWriter.beginObject(); + jsonWriter.name("code").value(key.mCode); + jsonWriter.name("altCode").value(key.getAltCode()); + jsonWriter.name("x").value(key.mX); + jsonWriter.name("y").value(key.mY); + jsonWriter.name("w").value(key.mWidth); + jsonWriter.name("h").value(key.mHeight); + jsonWriter.endObject(); + } + + /* package */ static void writeJson(final SuggestedWords words, final JsonWriter jsonWriter) + throws IOException { + jsonWriter.beginObject(); + jsonWriter.name("typedWordValid").value(words.mTypedWordValid); + jsonWriter.name("willAutoCorrect") + .value(words.mWillAutoCorrect); + jsonWriter.name("isPunctuationSuggestions") + .value(words.mIsPunctuationSuggestions); + jsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions); + jsonWriter.name("isPrediction").value(words.mIsPrediction); + jsonWriter.name("words"); + jsonWriter.beginArray(); + final int size = words.size(); + for (int j = 0; j < size; j++) { + final SuggestedWordInfo wordInfo = words.getWordInfo(j); + jsonWriter.value(wordInfo.toString()); + } + jsonWriter.endArray(); + jsonWriter.endObject(); + } +} diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java index ab9d2f857..884ade06b 100644 --- a/java/src/com/android/inputmethod/research/LogUnit.java +++ b/java/src/com/android/inputmethod/research/LogUnit.java @@ -16,10 +16,21 @@ package com.android.inputmethod.research; +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.define.ProductionFlag; import com.android.inputmethod.research.ResearchLogger.LogStatement; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * A group of log statements related to each other. @@ -36,6 +47,8 @@ import java.util.List; * been published recently, or whether the LogUnit contains numbers, etc. */ /* package */ class LogUnit { + private static final String TAG = LogUnit.class.getSimpleName(); + private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG; private final ArrayList mLogStatementList; private final ArrayList mValuesList; // Assume that mTimeList is sorted in increasing order. Do not insert null values into @@ -77,8 +90,13 @@ import java.util.List; mTimeList.add(time); } - public void publishTo(final ResearchLog researchLog, final boolean isIncludingPrivateData) { + /** + * Publish the contents of this LogUnit to researchLog. + */ + public synchronized void publishTo(final ResearchLog researchLog, + final boolean isIncludingPrivateData) { final int size = mLogStatementList.size(); + // Write out any logStatement that passes the privacy filter. for (int i = 0; i < size; i++) { final LogStatement logStatement = mLogStatementList.get(i); if (!isIncludingPrivateData && logStatement.mIsPotentiallyPrivate) { @@ -87,10 +105,72 @@ import java.util.List; if (mIsPartOfMegaword && logStatement.mIsPotentiallyRevealing) { continue; } - researchLog.outputEvent(mLogStatementList.get(i), mValuesList.get(i), mTimeList.get(i)); + // 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)); } } + 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 ? "" : 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; } diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java index 3c8731995..a6b1b889f 100644 --- a/java/src/com/android/inputmethod/research/ResearchLog.java +++ b/java/src/com/android/inputmethod/research/ResearchLog.java @@ -16,17 +16,10 @@ package com.android.inputmethod.research; -import android.content.SharedPreferences; -import android.os.SystemClock; 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.define.ProductionFlag; -import com.android.inputmethod.research.ResearchLogger.LogStatement; import java.io.BufferedWriter; import java.io.File; @@ -34,7 +27,6 @@ import java.io.FileWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; -import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; @@ -204,103 +196,17 @@ public class ResearchLog { } } - private static final String CURRENT_TIME_KEY = "_ct"; - private static final String UPTIME_KEY = "_ut"; - private static final String EVENT_TYPE_KEY = "_ty"; - - void outputEvent(final LogStatement logStatement, final Object[] values, final long time) { - // Not thread safe. - if (DEBUG) { - if (logStatement.mKeys.length != values.length) { - Log.d(TAG, "Key and Value list sizes do not match. " + logStatement.mName); - } - } + /** + * Return a JsonWriter for this ResearchLog. It is initialized the first time this method is + * called. The cached value is returned in future calls. + */ + public JsonWriter getValidJsonWriterLocked() { try { if (mJsonWriter == NULL_JSON_WRITER) { mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile))); mJsonWriter.beginArray(); mHasWrittenData = true; } - mJsonWriter.beginObject(); - mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis()); - mJsonWriter.name(UPTIME_KEY).value(time); - mJsonWriter.name(EVENT_TYPE_KEY).value(logStatement.mName); - final String[] keys = logStatement.mKeys; - final int length = values.length; - for (int i = 0; i < length; i++) { - mJsonWriter.name(keys[i]); - Object value = values[i]; - if (value instanceof CharSequence) { - mJsonWriter.value(value.toString()); - } else if (value instanceof Number) { - mJsonWriter.value((Number) value); - } else if (value instanceof Boolean) { - mJsonWriter.value((Boolean) value); - } else if (value instanceof CompletionInfo[]) { - CompletionInfo[] ci = (CompletionInfo[]) value; - mJsonWriter.beginArray(); - for (int j = 0; j < ci.length; j++) { - mJsonWriter.value(ci[j].toString()); - } - mJsonWriter.endArray(); - } else if (value instanceof SharedPreferences) { - SharedPreferences prefs = (SharedPreferences) value; - mJsonWriter.beginObject(); - for (Map.Entry entry : prefs.getAll().entrySet()) { - mJsonWriter.name(entry.getKey()); - final Object innerValue = entry.getValue(); - if (innerValue == null) { - mJsonWriter.nullValue(); - } else if (innerValue instanceof Boolean) { - mJsonWriter.value((Boolean) innerValue); - } else if (innerValue instanceof Number) { - mJsonWriter.value((Number) innerValue); - } else { - mJsonWriter.value(innerValue.toString()); - } - } - mJsonWriter.endObject(); - } else if (value instanceof Key[]) { - Key[] keyboardKeys = (Key[]) value; - mJsonWriter.beginArray(); - for (Key keyboardKey : keyboardKeys) { - mJsonWriter.beginObject(); - mJsonWriter.name("code").value(keyboardKey.mCode); - mJsonWriter.name("altCode").value(keyboardKey.getAltCode()); - mJsonWriter.name("x").value(keyboardKey.mX); - mJsonWriter.name("y").value(keyboardKey.mY); - mJsonWriter.name("w").value(keyboardKey.mWidth); - mJsonWriter.name("h").value(keyboardKey.mHeight); - mJsonWriter.endObject(); - } - mJsonWriter.endArray(); - } else if (value instanceof SuggestedWords) { - SuggestedWords words = (SuggestedWords) value; - mJsonWriter.beginObject(); - mJsonWriter.name("typedWordValid").value(words.mTypedWordValid); - mJsonWriter.name("willAutoCorrect").value(words.mWillAutoCorrect); - mJsonWriter.name("isPunctuationSuggestions") - .value(words.mIsPunctuationSuggestions); - mJsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions); - mJsonWriter.name("isPrediction").value(words.mIsPrediction); - mJsonWriter.name("words"); - mJsonWriter.beginArray(); - final int size = words.size(); - for (int j = 0; j < size; j++) { - SuggestedWordInfo wordInfo = words.getWordInfo(j); - mJsonWriter.value(wordInfo.toString()); - } - mJsonWriter.endArray(); - mJsonWriter.endObject(); - } else if (value == null) { - mJsonWriter.nullValue(); - } else { - Log.w(TAG, "Unrecognized type to be logged: " + - (value == null ? "" : value.getClass().getName())); - mJsonWriter.nullValue(); - } - } - mJsonWriter.endObject(); } catch (IOException e) { e.printStackTrace(); Log.w(TAG, "Error in JsonWriter; disabling logging"); @@ -315,5 +221,6 @@ public class ResearchLog { mJsonWriter = NULL_JSON_WRITER; } } + return mJsonWriter; } }