aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/research
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/research')
-rw-r--r--java/src/com/android/inputmethod/research/JsonUtils.java103
-rw-r--r--java/src/com/android/inputmethod/research/LogBuffer.java4
-rw-r--r--java/src/com/android/inputmethod/research/LogUnit.java220
-rw-r--r--java/src/com/android/inputmethod/research/MainLogBuffer.java20
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLog.java108
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogger.java1032
-rw-r--r--java/src/com/android/inputmethod/research/Statistics.java60
-rw-r--r--java/src/com/android/inputmethod/research/UploaderService.java19
8 files changed, 1014 insertions, 552 deletions
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<String,?> 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/LogBuffer.java b/java/src/com/android/inputmethod/research/LogBuffer.java
index ae7b1579a..a3c3e89de 100644
--- a/java/src/com/android/inputmethod/research/LogBuffer.java
+++ b/java/src/com/android/inputmethod/research/LogBuffer.java
@@ -110,4 +110,8 @@ public class LogBuffer {
}
return logUnit;
}
+
+ public boolean isEmpty() {
+ return mLogUnits.isEmpty();
+ }
}
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();
}
}
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
index 745768d35..0185e5fc0 100644
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -16,16 +16,23 @@
package com.android.inputmethod.research;
+import android.util.Log;
+
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.Suggest;
+import com.android.inputmethod.latin.define.ProductionFlag;
import java.util.Random;
public class MainLogBuffer extends LogBuffer {
+ private static final String TAG = MainLogBuffer.class.getSimpleName();
+ private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
+
// 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 =
+ ProductionFlag.IS_EXPERIMENTAL_DEBUG ? 2 : 18;
private final ResearchLog mResearchLog;
private Suggest mSuggest;
@@ -61,6 +68,9 @@ public class MainLogBuffer extends LogBuffer {
mWordsUntilSafeToSample--;
}
}
+ if (DEBUG) {
+ Log.d(TAG, "shiftedIn " + (newLogUnit.hasWord() ? newLogUnit.getWord() : ""));
+ }
}
public void resetWordCounter() {
@@ -104,12 +114,16 @@ 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 {
// Words not in the dictionary are a privacy threat.
- if (!(dictionary.isValidWord(word))) {
+ if (ResearchLogger.hasLetters(word) && !(dictionary.isValidWord(word))) {
+ if (DEBUG) {
+ Log.d(TAG, "NOT SAFE!: hasLetters: " + ResearchLogger.hasLetters(word)
+ + ", isValid: " + (dictionary.isValidWord(word)));
+ }
return false;
}
}
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 70c38e909..a6b1b889f 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -16,15 +16,9 @@
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 java.io.BufferedWriter;
@@ -33,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;
@@ -51,7 +44,7 @@ import java.util.concurrent.TimeUnit;
*/
public class ResearchLog {
private static final String TAG = ResearchLog.class.getSimpleName();
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
private static final int ABORT_TIMEOUT_IN_MS = 1000 * 4;
@@ -203,105 +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 String[] keys, final Object[] values) {
- // Not thread safe.
- if (keys.length == 0) {
- return;
- }
- if (DEBUG) {
- if (keys.length != values.length + 1) {
- Log.d(TAG, "Key and Value list sizes do not match. " + keys[0]);
- }
- }
+ /**
+ * 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(SystemClock.uptimeMillis());
- mJsonWriter.name(EVENT_TYPE_KEY).value(keys[0]);
- final int length = values.length;
- for (int i = 0; i < length; i++) {
- mJsonWriter.name(keys[i + 1]);
- 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<String,?> 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 ? "<null>" : value.getClass().getName()));
- mJsonWriter.nullValue();
- }
- }
- mJsonWriter.endObject();
} catch (IOException e) {
e.printStackTrace();
Log.w(TAG, "Error in JsonWriter; disabling logging");
@@ -316,5 +221,6 @@ public class ResearchLog {
mJsonWriter = NULL_JSON_WRITER;
}
}
+ return mJsonWriter;
}
}
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 763fd6e00..cde100a5f 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -34,7 +34,6 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
-import android.inputmethodservice.InputMethodService;
import android.net.Uri;
import android.os.Build;
import android.os.IBinder;
@@ -47,7 +46,6 @@ import android.view.MotionEvent;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.Toast;
@@ -57,9 +55,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;
@@ -84,19 +82,29 @@ import java.util.UUID;
*/
public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = ResearchLogger.class.getSimpleName();
- private static final boolean DEBUG = false;
- private static final boolean OUTPUT_ENTIRE_BUFFER = false; // true may disclose private info
+ private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
+ // Whether all n-grams should be logged. true will disclose private info.
+ private static final boolean LOG_EVERYTHING = false
+ && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
+ // Whether the TextView contents are logged at the end of the session. true will disclose
+ // private info.
+ private static final boolean LOG_FULL_TEXTVIEW_CONTENTS = false
+ && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
public static final boolean DEFAULT_USABILITY_STUDY_MODE = false;
/* package */ static boolean sIsLogging = false;
- private static final int OUTPUT_FORMAT_VERSION = 1;
+ private static final int OUTPUT_FORMAT_VERSION = 5;
private static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
private static final String PREF_RESEARCH_HAS_SEEN_SPLASH = "pref_research_has_seen_splash";
/* package */ static final String FILENAME_PREFIX = "researchLog";
private static final String FILENAME_SUFFIX = ".txt";
private static final SimpleDateFormat TIMESTAMP_DATEFORMAT =
new SimpleDateFormat("yyyyMMddHHmmssS", Locale.US);
+ // Whether to show an indicator on the screen that logging is on. Currently a very small red
+ // dot in the lower right hand corner. Most users should not notice it.
private static final boolean IS_SHOWING_INDICATOR = true;
- private static final boolean IS_SHOWING_INDICATOR_CLEARLY = false;
+ // Change the default indicator to something very visible. Currently two red vertical bars on
+ // either side of they keyboard.
+ private static final boolean IS_SHOWING_INDICATOR_CLEARLY = false || LOG_EVERYTHING;
public static final int FEEDBACK_WORD_BUFFER_SIZE = 5;
// constants related to specific log points
@@ -137,13 +145,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
// used to check whether words are not unique
private Suggest mSuggest;
- private Dictionary mDictionary;
private MainKeyboardView mMainKeyboardView;
- private InputMethodService mInputMethodService;
+ private LatinIME mLatinIME;
private final Statistics mStatistics;
private Intent mUploadIntent;
- private PendingIntent mUploadPendingIntent;
private LogUnit mCurrentLogUnit = new LogUnit();
@@ -155,12 +161,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
return sInstance;
}
- public void init(final InputMethodService ims, final SharedPreferences prefs) {
- assert ims != null;
- if (ims == null) {
+ public void init(final LatinIME latinIME, final SharedPreferences prefs) {
+ assert latinIME != null;
+ if (latinIME == null) {
Log.w(TAG, "IMS is null; logging is off");
} else {
- mFilesDir = ims.getFilesDir();
+ mFilesDir = latinIME.getFilesDir();
if (mFilesDir == null || !mFilesDir.exists()) {
Log.w(TAG, "IME storage directory does not exist.");
}
@@ -185,13 +191,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
e.apply();
}
}
- mInputMethodService = ims;
+ mLatinIME = latinIME;
mPrefs = prefs;
- mUploadIntent = new Intent(mInputMethodService, UploaderService.class);
- mUploadPendingIntent = PendingIntent.getService(mInputMethodService, 0, mUploadIntent, 0);
+ mUploadIntent = new Intent(mLatinIME, UploaderService.class);
if (ProductionFlag.IS_EXPERIMENTAL) {
- scheduleUploadingService(mInputMethodService);
+ scheduleUploadingService(mLatinIME);
}
}
@@ -250,7 +255,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (windowToken == null) {
return;
}
- final AlertDialog.Builder builder = new AlertDialog.Builder(mInputMethodService)
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mLatinIME)
.setTitle(R.string.research_splash_title)
.setMessage(R.string.research_splash_content)
.setPositiveButton(android.R.string.yes,
@@ -265,12 +270,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- final String packageName = mInputMethodService.getPackageName();
+ final String packageName = mLatinIME.getPackageName();
final Uri packageUri = Uri.parse("package:" + packageName);
final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE,
packageUri);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mInputMethodService.startActivity(intent);
+ mLatinIME.startActivity(intent);
}
})
.setCancelable(true)
@@ -278,7 +283,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
- mInputMethodService.requestHideSelf(0);
+ mLatinIME.requestHideSelf(0);
}
});
mSplashDialog = builder.create();
@@ -322,10 +327,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
private void checkForEmptyEditor() {
- if (mInputMethodService == null) {
+ if (mLatinIME == null) {
return;
}
- final InputConnection ic = mInputMethodService.getCurrentInputConnection();
+ final InputConnection ic = mLatinIME.getCurrentInputConnection();
if (ic == null) {
return;
}
@@ -378,11 +383,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (DEBUG) {
Log.d(TAG, "stop called");
}
- logStatistics();
commitCurrentLogUnit();
if (mMainLogBuffer != null) {
- publishLogBuffer(mMainLogBuffer, mMainResearchLog, false /* isIncludingPrivateData */);
+ publishLogBuffer(mMainLogBuffer, mMainResearchLog,
+ LOG_EVERYTHING /* isIncludingPrivateData */);
mMainResearchLog.close(null /* callback */);
mMainLogBuffer = null;
}
@@ -427,21 +432,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) {
@@ -539,9 +529,40 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
*/
- private static final String[] EVENTKEYS_FEEDBACK = {
- "UserTimestamp", "contents"
- };
+ static class LogStatement {
+ final String mName;
+
+ // mIsPotentiallyPrivate indicates that event contains potentially private information. If
+ // the word that this event is a part of is determined to be privacy-sensitive, then this
+ // event should not be included in the output log. The system waits to output until the
+ // containing word is known.
+ final boolean mIsPotentiallyPrivate;
+
+ // mIsPotentiallyRevealing indicates that this statement may disclose details about other
+ // words typed in other LogUnits. This can happen if the user is not inserting spaces, and
+ // data from Suggestions and/or Composing text reveals the entire "megaword". For example,
+ // say the user is typing "for the win", and the system wants to record the bigram "the
+ // win". If the user types "forthe", omitting the space, the system will give "for the" as
+ // a suggestion. If the user accepts the autocorrection, the suggestion for "for the" is
+ // included in the log for the word "the", disclosing that the previous word had been "for".
+ // For now, we simply do not include this data when logging part of a "megaword".
+ final boolean mIsPotentiallyRevealing;
+
+ // mKeys stores the names that are the attributes in the output json objects
+ final String[] mKeys;
+ private static final String[] NULL_KEYS = new String[0];
+
+ LogStatement(final String name, final boolean isPotentiallyPrivate,
+ final boolean isPotentiallyRevealing, final String... keys) {
+ mName = name;
+ mIsPotentiallyPrivate = isPotentiallyPrivate;
+ mIsPotentiallyRevealing = isPotentiallyRevealing;
+ mKeys = (keys == null) ? NULL_KEYS : keys;
+ }
+ }
+
+ private static final LogStatement LOGSTATEMENT_FEEDBACK =
+ new LogStatement("UserTimestamp", false, false, "contents");
public void sendFeedback(final String feedbackContents, final boolean includeHistory) {
if (mFeedbackLogBuffer == null) {
return;
@@ -552,11 +573,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
mFeedbackLogBuffer.clear();
}
final LogUnit feedbackLogUnit = new LogUnit();
- final Object[] values = {
- feedbackContents
- };
- feedbackLogUnit.addLogStatement(EVENTKEYS_FEEDBACK, values,
- false /* isPotentiallyPrivate */);
+ feedbackLogUnit.addLogStatement(LOGSTATEMENT_FEEDBACK, SystemClock.uptimeMillis(),
+ feedbackContents);
mFeedbackLogBuffer.shiftIn(feedbackLogUnit);
publishLogBuffer(mFeedbackLogBuffer, mFeedbackLog, true /* isIncludingPrivateData */);
mFeedbackLog.close(new Runnable() {
@@ -572,7 +590,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (DEBUG) {
Log.d(TAG, "calling uploadNow()");
}
- mInputMethodService.startService(mUploadIntent);
+ mLatinIME.startService(mUploadIntent);
}
public void onLeavingSendFeedbackDialog() {
@@ -586,6 +604,13 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
}
+ private Dictionary getDictionary() {
+ if (mSuggest == null) {
+ return null;
+ }
+ return mSuggest.getMainDictionary();
+ }
+
private void setIsPasswordView(boolean isPasswordView) {
mIsPasswordView = isPasswordView;
}
@@ -626,7 +651,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
final float savedStrokeWidth = paint.getStrokeWidth();
if (IS_SHOWING_INDICATOR_CLEARLY) {
paint.setStrokeWidth(5);
- canvas.drawRect(0, 0, width, height, paint);
+ canvas.drawLine(0, 0, 0, height, paint);
+ canvas.drawLine(width, 0, width, height, paint);
} else {
// Put a tiny red dot on the screen so a knowledgeable user can check whether
// it is enabled. The dot is actually a zero-width, zero-height rectangle,
@@ -641,58 +667,30 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
}
- private static final Object[] EVENTKEYS_NULLVALUES = {};
-
/**
* Buffer a research log event, flagging it as privacy-sensitive.
- *
- * This event contains potentially private information. If the word that this event is a part
- * of is determined to be privacy-sensitive, then this event should not be included in the
- * output log. The system waits to output until the containing word is known.
- *
- * @param keys an array containing a descriptive name for the event, followed by the keys
- * @param values an array of values, either a String or Number. length should be one
- * less than the keys array
*/
- private synchronized void enqueuePotentiallyPrivateEvent(final String[] keys,
- final Object[] values) {
- assert values.length + 1 == keys.length;
+ private synchronized void enqueueEvent(LogStatement logStatement, Object... values) {
+ assert values.length == logStatement.mKeys.length;
if (isAllowedToLog()) {
- mCurrentLogUnit.addLogStatement(keys, values, true /* isPotentiallyPrivate */);
+ final long time = SystemClock.uptimeMillis();
+ mCurrentLogUnit.addLogStatement(logStatement, time, values);
}
}
private void setCurrentLogUnitContainsDigitFlag() {
- mCurrentLogUnit.setContainsDigit();
- }
-
- /**
- * Buffer a research log event, flaggint it as not privacy-sensitive.
- *
- * This event contains no potentially private information. Even if the word that this event
- * is privacy-sensitive, this event can still safely be sent to the output log. The system
- * waits until the containing word is known so that this event can be written in the proper
- * temporal order with other events that may be privacy sensitive.
- *
- * @param keys an array containing a descriptive name for the event, followed by the keys
- * @param values an array of values, either a String or Number. length should be one
- * less than the keys array
- */
- private synchronized void enqueueEvent(final String[] keys, final Object[] values) {
- assert values.length + 1 == keys.length;
- if (isAllowedToLog()) {
- mCurrentLogUnit.addLogStatement(keys, values, false /* isPotentiallyPrivate */);
- }
+ mCurrentLogUnit.setMayContainDigit();
}
/* package for test */ void commitCurrentLogUnit() {
if (DEBUG) {
- Log.d(TAG, "commitCurrentLogUnit");
+ Log.d(TAG, "commitCurrentLogUnit" + (mCurrentLogUnit.hasWord() ?
+ ": " + mCurrentLogUnit.getWord() : ""));
}
if (!mCurrentLogUnit.isEmpty()) {
if (mMainLogBuffer != null) {
mMainLogBuffer.shiftIn(mCurrentLogUnit);
- if (mMainLogBuffer.isSafeToLog() && mMainResearchLog != null) {
+ if ((mMainLogBuffer.isSafeToLog() || LOG_EVERYTHING) && mMainResearchLog != null) {
publishLogBuffer(mMainLogBuffer, mMainResearchLog,
true /* isIncludingPrivateData */);
mMainLogBuffer.resetWordCounter();
@@ -702,36 +700,59 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
mFeedbackLogBuffer.shiftIn(mCurrentLogUnit);
}
mCurrentLogUnit = new LogUnit();
- Log.d(TAG, "commitCurrentLogUnit");
}
}
+ 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);
/* package for test */ void publishLogBuffer(final LogBuffer logBuffer,
final ResearchLog researchLog, final boolean isIncludingPrivateData) {
+ final LogUnit openingLogUnit = new LogUnit();
+ if (logBuffer.isEmpty()) return;
+ openingLogUnit.addLogStatement(LOGSTATEMENT_LOG_SEGMENT_OPENING, SystemClock.uptimeMillis(),
+ isIncludingPrivateData);
+ researchLog.publish(openingLogUnit, true /* isIncludingPrivateData */);
LogUnit logUnit;
while ((logUnit = logBuffer.shiftOut()) != null) {
+ if (DEBUG) {
+ Log.d(TAG, "publishLogBuffer: " + (logUnit.hasWord() ? logUnit.getWord()
+ : "<wordless>"));
+ }
researchLog.publish(logUnit, isIncludingPrivateData);
}
+ final LogUnit closingLogUnit = new LogUnit();
+ closingLogUnit.addLogStatement(LOGSTATEMENT_LOG_SEGMENT_CLOSING,
+ SystemClock.uptimeMillis());
+ researchLog.publish(closingLogUnit, true /* isIncludingPrivateData */);
}
- private boolean hasOnlyLetters(final String word) {
+ public static 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) {
- Log.d(TAG, "onWordComplete: " + word);
- if (word != null && word.length() > 0 && hasOnlyLetters(word)) {
+ private static final LogStatement LOGSTATEMENT_COMMIT_RECORD_SPLIT_WORDS =
+ new LogStatement("recordSplitWords", true, false);
+ public void onWordComplete(final String word, final long maxTime) {
+ final Dictionary dictionary = getDictionary();
+ if (word != null && word.length() > 0 && hasLetters(word)) {
mCurrentLogUnit.setWord(word);
- mStatistics.recordWordEntered();
+ final boolean isDictionaryWord = dictionary != null
+ && dictionary.isValidWord(word);
+ mStatistics.recordWordEntered(isDictionaryWord);
}
+ final LogUnit newLogUnit = mCurrentLogUnit.splitByTime(maxTime);
+ enqueueCommitText(word);
commitCurrentLogUnit();
+ mCurrentLogUnit = newLogUnit;
}
private static int scrubDigitFromCodePoint(int codePoint) {
@@ -775,70 +796,90 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
private String scrubWord(String word) {
- if (mDictionary == null) {
+ final Dictionary dictionary = getDictionary();
+ if (dictionary == null) {
return WORD_REPLACEMENT_STRING;
}
- if (mDictionary.isValidWord(word)) {
+ if (dictionary.isValidWord(word)) {
return word;
}
return WORD_REPLACEMENT_STRING;
}
- private static final String[] EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL = {
- "LatinIMEOnStartInputViewInternal", "uuid", "packageName", "inputType", "imeOptions",
- "fieldId", "display", "model", "prefs", "versionCode", "versionName", "outputFormatVersion"
- };
+ // Specific logging methods follow below. The comments for each logging method should
+ // indicate what specific method is logged, and how to trigger it from the user interface.
+ //
+ // Logging methods can be generally classified into two flavors, "UserAction", which should
+ // correspond closely to an event that is sensed by the IME, and is usually generated
+ // directly by the user, and "SystemResponse" which corresponds to an event that the IME
+ // generates, often after much processing of user input. SystemResponses should correspond
+ // closely to user-visible events.
+ // TODO: Consider exposing the UserAction classification in the log output.
+
+ /**
+ * Log a call to LatinIME.onStartInputViewInternal().
+ *
+ * UserAction: called each time the keyboard is opened up.
+ */
+ private static final LogStatement LOGSTATEMENT_LATIN_IME_ON_START_INPUT_VIEW_INTERNAL =
+ new LogStatement("LatinImeOnStartInputViewInternal", false, false, "uuid",
+ "packageName", "inputType", "imeOptions", "fieldId", "display", "model",
+ "prefs", "versionCode", "versionName", "outputFormatVersion", "logEverything",
+ "isExperimentalDebug");
public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
final SharedPreferences prefs) {
final ResearchLogger researchLogger = getInstance();
- researchLogger.start();
if (editorInfo != null) {
- final Context context = researchLogger.mInputMethodService;
+ final boolean isPassword = InputTypeUtils.isPasswordInputType(editorInfo.inputType)
+ || InputTypeUtils.isVisiblePasswordInputType(editorInfo.inputType);
+ getInstance().setIsPasswordView(isPassword);
+ researchLogger.start();
+ final Context context = researchLogger.mLatinIME;
try {
final PackageInfo packageInfo;
packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(),
0);
final Integer versionCode = packageInfo.versionCode;
final String versionName = packageInfo.versionName;
- final Object[] values = {
+ researchLogger.enqueueEvent(LOGSTATEMENT_LATIN_IME_ON_START_INPUT_VIEW_INTERNAL,
researchLogger.mUUIDString, editorInfo.packageName,
Integer.toHexString(editorInfo.inputType),
Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId,
Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName,
- OUTPUT_FORMAT_VERSION
- };
- researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL, values);
+ OUTPUT_FORMAT_VERSION, LOG_EVERYTHING,
+ ProductionFlag.IS_EXPERIMENTAL_DEBUG);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
}
}
- public void latinIME_onFinishInputInternal() {
+ public void latinIME_onFinishInputViewInternal() {
+ logStatistics();
stop();
}
- private static final String[] EVENTKEYS_USER_FEEDBACK = {
- "UserFeedback", "FeedbackContents"
- };
-
- private static final String[] EVENTKEYS_PREFS_CHANGED = {
- "PrefsChanged", "prefs"
- };
+ /**
+ * Log a change in preferences.
+ *
+ * UserAction: called when the user changes the settings.
+ */
+ private static final LogStatement LOGSTATEMENT_PREFS_CHANGED =
+ new LogStatement("PrefsChanged", false, false, "prefs");
public static void prefsChanged(final SharedPreferences prefs) {
final ResearchLogger researchLogger = getInstance();
- final Object[] values = {
- prefs
- };
- researchLogger.enqueueEvent(EVENTKEYS_PREFS_CHANGED, values);
+ researchLogger.enqueueEvent(LOGSTATEMENT_PREFS_CHANGED, prefs);
}
- // Regular logging methods
-
- private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_PROCESSMOTIONEVENT = {
- "MainKeyboardViewProcessMotionEvent", "action", "eventTime", "id", "x", "y", "size",
- "pressure"
- };
+ /**
+ * Log a call to MainKeyboardView.processMotionEvent().
+ *
+ * UserAction: called when the user puts their finger onto the screen (ACTION_DOWN).
+ *
+ */
+ private static final LogStatement LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT =
+ new LogStatement("MainKeyboardViewProcessMotionEvent", true, false, "action",
+ "eventTime", "id", "x", "y", "size", "pressure");
public static void mainKeyboardView_processMotionEvent(final MotionEvent me, final int action,
final long eventTime, final int index, final int id, final int x, final int y) {
if (me != null) {
@@ -855,40 +896,44 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
final float size = me.getSize(index);
final float pressure = me.getPressure(index);
- final Object[] values = {
- actionString, eventTime, id, x, y, size, pressure
- };
- getInstance().enqueuePotentiallyPrivateEvent(
- EVENTKEYS_MAINKEYBOARDVIEW_PROCESSMOTIONEVENT, values);
+ getInstance().enqueueEvent(LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT,
+ actionString, eventTime, id, x, y, size, pressure);
}
}
- private static final String[] EVENTKEYS_LATINIME_ONCODEINPUT = {
- "LatinIMEOnCodeInput", "code", "x", "y"
- };
+ /**
+ * Log a call to LatinIME.onCodeInput().
+ *
+ * SystemResponse: The main processing step for entering text. Called when the user performs a
+ * tap, a flick, a long press, releases a gesture, or taps a punctuation suggestion.
+ */
+ private static final LogStatement LOGSTATEMENT_LATIN_IME_ON_CODE_INPUT =
+ new LogStatement("LatinImeOnCodeInput", true, false, "code", "x", "y");
public static void latinIME_onCodeInput(final int code, final int x, final int y) {
final long time = SystemClock.uptimeMillis();
final ResearchLogger researchLogger = getInstance();
- final Object[] values = {
- Keyboard.printableCode(scrubDigitFromCodePoint(code)), x, y
- };
- researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values);
+ researchLogger.enqueueEvent(LOGSTATEMENT_LATIN_IME_ON_CODE_INPUT,
+ Constants.printableCode(scrubDigitFromCodePoint(code)), x, y);
if (Character.isDigit(code)) {
researchLogger.setCurrentLogUnitContainsDigitFlag();
}
researchLogger.mStatistics.recordChar(code, time);
}
-
- private static final String[] EVENTKEYS_LATINIME_ONDISPLAYCOMPLETIONS = {
- "LatinIMEOnDisplayCompletions", "applicationSpecifiedCompletions"
- };
+ /**
+ * Log a call to LatinIME.onDisplayCompletions().
+ *
+ * SystemResponse: The IME has displayed application-specific completions. They may show up
+ * in the suggestion strip, such as a landscape phone.
+ */
+ private static final LogStatement LOGSTATEMENT_LATINIME_ONDISPLAYCOMPLETIONS =
+ new LogStatement("LatinIMEOnDisplayCompletions", true, true,
+ "applicationSpecifiedCompletions");
public static void latinIME_onDisplayCompletions(
final CompletionInfo[] applicationSpecifiedCompletions) {
- final Object[] values = {
- applicationSpecifiedCompletions
- };
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONDISPLAYCOMPLETIONS,
- values);
+ // Note; passing an array as a single element in a vararg list. Must create a new
+ // dummy array around it or it will get expanded.
+ getInstance().enqueueEvent(LOGSTATEMENT_LATINIME_ONDISPLAYCOMPLETIONS,
+ new Object[] { applicationSpecifiedCompletions });
}
public static boolean getAndClearLatinIMEExpectingUpdateSelection() {
@@ -897,27 +942,35 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
return returnValue;
}
- private static final String[] EVENTKEYS_LATINIME_ONWINDOWHIDDEN = {
- "LatinIMEOnWindowHidden", "isTextTruncated", "text"
- };
+ /**
+ * Log a call to LatinIME.onWindowHidden().
+ *
+ * UserAction: The user has performed an action that has caused the IME to be closed. They may
+ * have focused on something other than a text field, or explicitly closed it.
+ */
+ private static final LogStatement LOGSTATEMENT_LATINIME_ONWINDOWHIDDEN =
+ new LogStatement("LatinIMEOnWindowHidden", false, false, "isTextTruncated", "text");
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) {
+ final boolean isTextTruncated;
+ final String text;
+ if (LOG_FULL_TEXTVIEW_CONTENTS) {
+ // 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] = "";
+ isTextTruncated = false;
+ text = "";
} else {
if (charSequence.length() > MAX_INPUTVIEW_LENGTH_TO_CAPTURE) {
int length = MAX_INPUTVIEW_LENGTH_TO_CAPTURE;
@@ -928,29 +981,39 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
final CharSequence truncatedCharSequence = charSequence.subSequence(0,
length);
- values[0] = true;
- values[1] = truncatedCharSequence.toString();
+ isTextTruncated = true;
+ text = truncatedCharSequence.toString();
} else {
- values[0] = false;
- values[1] = charSequence.toString();
+ isTextTruncated = false;
+ text = charSequence.toString();
}
}
} else {
- values[0] = true;
- values[1] = "";
+ isTextTruncated = true;
+ text = "";
}
final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONWINDOWHIDDEN, values);
+ // Assume that OUTPUT_ENTIRE_BUFFER is only true when we don't care about privacy (e.g.
+ // during a live user test), so the normal isPotentiallyPrivate and
+ // isPotentiallyRevealing flags do not apply
+ researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONWINDOWHIDDEN, isTextTruncated,
+ text);
researchLogger.commitCurrentLogUnit();
getInstance().stop();
}
}
- private static final String[] EVENTKEYS_LATINIME_ONUPDATESELECTION = {
- "LatinIMEOnUpdateSelection", "lastSelectionStart", "lastSelectionEnd", "oldSelStart",
- "oldSelEnd", "newSelStart", "newSelEnd", "composingSpanStart", "composingSpanEnd",
- "expectingUpdateSelection", "expectingUpdateSelectionFromLogger", "context"
- };
+ /**
+ * Log a call to LatinIME.onUpdateSelection().
+ *
+ * UserAction/SystemResponse: The user has moved the cursor or selection. This function may
+ * be called, however, when the system has moved the cursor, say by inserting a character.
+ */
+ private static final LogStatement LOGSTATEMENT_LATINIME_ONUPDATESELECTION =
+ new LogStatement("LatinIMEOnUpdateSelection", true, false, "lastSelectionStart",
+ "lastSelectionEnd", "oldSelStart", "oldSelEnd", "newSelStart", "newSelEnd",
+ "composingSpanStart", "composingSpanEnd", "expectingUpdateSelection",
+ "expectingUpdateSelectionFromLogger", "context");
public static void latinIME_onUpdateSelection(final int lastSelectionStart,
final int lastSelectionEnd, final int oldSelStart, final int oldSelEnd,
final int newSelStart, final int newSelEnd, final int composingSpanStart,
@@ -966,350 +1029,471 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
final ResearchLogger researchLogger = getInstance();
final String scrubbedWord = researchLogger.scrubWord(word);
- final Object[] values = {
- lastSelectionStart, lastSelectionEnd, oldSelStart, oldSelEnd, newSelStart,
- newSelEnd, composingSpanStart, composingSpanEnd, expectingUpdateSelection,
- expectingUpdateSelectionFromLogger, scrubbedWord
- };
- researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONUPDATESELECTION, values);
+ researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONUPDATESELECTION, lastSelectionStart,
+ lastSelectionEnd, oldSelStart, oldSelEnd, newSelStart, newSelEnd,
+ composingSpanStart, composingSpanEnd, expectingUpdateSelection,
+ expectingUpdateSelectionFromLogger, scrubbedWord);
}
- private static final String[] EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY = {
- "LatinIMEPickSuggestionManually", "replacedWord", "index", "suggestion", "x", "y"
- };
+ /**
+ * Log a call to LatinIME.pickSuggestionManually().
+ *
+ * UserAction: The user has chosen a specific word from the suggestion strip.
+ */
+ private static final LogStatement LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY =
+ new LogStatement("LatinIMEPickSuggestionManually", true, false, "replacedWord", "index",
+ "suggestion", "x", "y");
public static void latinIME_pickSuggestionManually(final String replacedWord,
- final int index, CharSequence suggestion) {
- final Object[] values = {
- scrubDigitsFromString(replacedWord), index,
- (suggestion == null ? null : scrubDigitsFromString(suggestion.toString())),
- Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE
- };
+ final int index, final String suggestion) {
+ final String scrubbedWord = scrubDigitsFromString(suggestion);
final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY,
- values);
- }
-
- private static final String[] EVENTKEYS_LATINIME_PUNCTUATIONSUGGESTION = {
- "LatinIMEPunctuationSuggestion", "index", "suggestion", "x", "y"
- };
- public static void latinIME_punctuationSuggestion(final int index,
- final CharSequence suggestion) {
- final Object[] values = {
- index, suggestion,
- Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE
- };
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_PUNCTUATIONSUGGESTION, values);
+ researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY,
+ scrubDigitsFromString(replacedWord), index,
+ suggestion == null ? null : scrubbedWord, Constants.SUGGESTION_STRIP_COORDINATE,
+ Constants.SUGGESTION_STRIP_COORDINATE);
+ researchLogger.onWordComplete(scrubbedWord, Long.MAX_VALUE);
+ researchLogger.mStatistics.recordManualSuggestion();
+ }
+
+ /**
+ * Log a call to LatinIME.punctuationSuggestion().
+ *
+ * UserAction: The user has chosen punctuation from the punctuation suggestion strip.
+ */
+ private static final LogStatement LOGSTATEMENT_LATINIME_PUNCTUATIONSUGGESTION =
+ new LogStatement("LatinIMEPunctuationSuggestion", false, false, "index", "suggestion",
+ "x", "y");
+ public static void latinIME_punctuationSuggestion(final int index, final String suggestion) {
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PUNCTUATIONSUGGESTION, index, suggestion,
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
+ researchLogger.onWordComplete(suggestion, Long.MAX_VALUE);
}
- private static final String[] EVENTKEYS_LATINIME_SENDKEYCODEPOINT = {
- "LatinIMESendKeyCodePoint", "code"
- };
+ /**
+ * Log a call to LatinIME.sendKeyCodePoint().
+ *
+ * SystemResponse: The IME is simulating a hardware keypress. This happens for numbers; other
+ * input typically goes through RichInputConnection.setComposingText() and
+ * RichInputConnection.commitText().
+ */
+ private static final LogStatement LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT =
+ new LogStatement("LatinIMESendKeyCodePoint", true, false, "code");
public static void latinIME_sendKeyCodePoint(final int code) {
- final Object[] values = {
- Keyboard.printableCode(scrubDigitFromCodePoint(code))
- };
final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values);
+ researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT,
+ Constants.printableCode(scrubDigitFromCodePoint(code)));
if (Character.isDigit(code)) {
researchLogger.setCurrentLogUnitContainsDigitFlag();
}
}
- private static final String[] EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACE = {
- "LatinIMESwapSwapperAndSpace"
- };
+ /**
+ * Log a call to LatinIME.swapSwapperAndSpace().
+ *
+ * SystemResponse: A symbol has been swapped with a space character. E.g. punctuation may swap
+ * if a soft space is inserted after a word.
+ */
+ private static final LogStatement LOGSTATEMENT_LATINIME_SWAPSWAPPERANDSPACE =
+ new LogStatement("LatinIMESwapSwapperAndSpace", false, false);
public static void latinIME_swapSwapperAndSpace() {
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACE, EVENTKEYS_NULLVALUES);
+ getInstance().enqueueEvent(LOGSTATEMENT_LATINIME_SWAPSWAPPERANDSPACE);
}
- private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_ONLONGPRESS = {
- "MainKeyboardViewOnLongPress"
- };
+ /**
+ * Log a call to MainKeyboardView.onLongPress().
+ *
+ * UserAction: The user has performed a long-press on a key.
+ */
+ private static final LogStatement LOGSTATEMENT_MAINKEYBOARDVIEW_ONLONGPRESS =
+ new LogStatement("MainKeyboardViewOnLongPress", false, false);
public static void mainKeyboardView_onLongPress() {
- getInstance().enqueueEvent(EVENTKEYS_MAINKEYBOARDVIEW_ONLONGPRESS, EVENTKEYS_NULLVALUES);
+ getInstance().enqueueEvent(LOGSTATEMENT_MAINKEYBOARDVIEW_ONLONGPRESS);
}
- private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_SETKEYBOARD = {
- "MainKeyboardViewSetKeyboard", "elementId", "locale", "orientation", "width",
- "modeName", "action", "navigateNext", "navigatePrevious", "clobberSettingsKey",
- "passwordInput", "shortcutKeyEnabled", "hasShortcutKey", "languageSwitchKeyEnabled",
- "isMultiLine", "tw", "th", "keys"
- };
+ /**
+ * Log a call to MainKeyboardView.setKeyboard().
+ *
+ * SystemResponse: The IME has switched to a new keyboard (e.g. French, English).
+ * This is typically called right after LatinIME.onStartInputViewInternal (when starting a new
+ * IME), but may happen at other times if the user explicitly requests a keyboard change.
+ */
+ private static final LogStatement LOGSTATEMENT_MAINKEYBOARDVIEW_SETKEYBOARD =
+ new LogStatement("MainKeyboardViewSetKeyboard", false, false, "elementId", "locale",
+ "orientation", "width", "modeName", "action", "navigateNext",
+ "navigatePrevious", "clobberSettingsKey", "passwordInput", "shortcutKeyEnabled",
+ "hasShortcutKey", "languageSwitchKeyEnabled", "isMultiLine", "tw", "th",
+ "keys");
public static void mainKeyboardView_setKeyboard(final Keyboard keyboard) {
- if (keyboard != null) {
- final KeyboardId kid = keyboard.mId;
- final boolean isPasswordView = kid.passwordInput();
- getInstance().setIsPasswordView(isPasswordView);
- final Object[] values = {
+ final KeyboardId kid = keyboard.mId;
+ final boolean isPasswordView = kid.passwordInput();
+ getInstance().setIsPasswordView(isPasswordView);
+ getInstance().enqueueEvent(LOGSTATEMENT_MAINKEYBOARDVIEW_SETKEYBOARD,
KeyboardId.elementIdToName(kid.mElementId),
kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
- kid.mOrientation,
- kid.mWidth,
- KeyboardId.modeName(kid.mMode),
- kid.imeAction(),
- kid.navigateNext(),
- kid.navigatePrevious(),
- kid.mClobberSettingsKey,
- isPasswordView,
- kid.mShortcutKeyEnabled,
- kid.mHasShortcutKey,
- kid.mLanguageSwitchKeyEnabled,
- kid.isMultiLine(),
- keyboard.mOccupiedWidth,
- keyboard.mOccupiedHeight,
- keyboard.mKeys
- };
- getInstance().setIsPasswordView(isPasswordView);
- getInstance().enqueueEvent(EVENTKEYS_MAINKEYBOARDVIEW_SETKEYBOARD, values);
- }
+ kid.mOrientation, kid.mWidth, KeyboardId.modeName(kid.mMode), kid.imeAction(),
+ kid.navigateNext(), kid.navigatePrevious(), kid.mClobberSettingsKey,
+ isPasswordView, kid.mShortcutKeyEnabled, kid.mHasShortcutKey,
+ kid.mLanguageSwitchKeyEnabled, kid.isMultiLine(), keyboard.mOccupiedWidth,
+ keyboard.mOccupiedHeight, keyboard.mKeys);
}
- private static final String[] EVENTKEYS_LATINIME_REVERTCOMMIT = {
- "LatinIMERevertCommit", "originallyTypedWord"
- };
- public static void latinIME_revertCommit(final String originallyTypedWord) {
- final Object[] values = {
- originallyTypedWord
- };
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_REVERTCOMMIT, values);
+ /**
+ * Log a call to LatinIME.revertCommit().
+ *
+ * SystemResponse: The IME has reverted commited text. This happens when the user enters
+ * a word, commits it by pressing space or punctuation, and then reverts the commit by hitting
+ * backspace.
+ */
+ private static final LogStatement LOGSTATEMENT_LATINIME_REVERTCOMMIT =
+ new LogStatement("LatinIMERevertCommit", true, false, "committedWord",
+ "originallyTypedWord");
+ public static void latinIME_revertCommit(final String committedWord,
+ final String originallyTypedWord) {
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_REVERTCOMMIT, committedWord,
+ originallyTypedWord);
+ researchLogger.mStatistics.recordRevertCommit();
}
- private static final String[] EVENTKEYS_POINTERTRACKER_CALLLISTENERONCANCELINPUT = {
- "PointerTrackerCallListenerOnCancelInput"
- };
+ /**
+ * Log a call to PointerTracker.callListenerOnCancelInput().
+ *
+ * UserAction: The user has canceled the input, e.g., by pressing down, but then removing
+ * outside the keyboard area.
+ * TODO: Verify
+ */
+ private static final LogStatement LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCANCELINPUT =
+ new LogStatement("PointerTrackerCallListenerOnCancelInput", false, false);
public static void pointerTracker_callListenerOnCancelInput() {
- getInstance().enqueueEvent(EVENTKEYS_POINTERTRACKER_CALLLISTENERONCANCELINPUT,
- EVENTKEYS_NULLVALUES);
+ getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCANCELINPUT);
}
- private static final String[] EVENTKEYS_POINTERTRACKER_CALLLISTENERONCODEINPUT = {
- "PointerTrackerCallListenerOnCodeInput", "code", "outputText", "x", "y",
- "ignoreModifierKey", "altersCode", "isEnabled"
- };
+ /**
+ * Log a call to PointerTracker.callListenerOnCodeInput().
+ *
+ * SystemResponse: The user has entered a key through the normal tapping mechanism.
+ * LatinIME.onCodeInput will also be called.
+ */
+ private static final LogStatement LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCODEINPUT =
+ new LogStatement("PointerTrackerCallListenerOnCodeInput", true, false, "code",
+ "outputText", "x", "y", "ignoreModifierKey", "altersCode", "isEnabled");
public static void pointerTracker_callListenerOnCodeInput(final Key key, final int x,
final int y, final boolean ignoreModifierKey, final boolean altersCode,
final int code) {
if (key != null) {
String outputText = key.getOutputText();
- final Object[] values = {
- Keyboard.printableCode(scrubDigitFromCodePoint(code)), outputText == null ? null
- : scrubDigitsFromString(outputText.toString()),
- x, y, ignoreModifierKey, altersCode, key.isEnabled()
- };
- getInstance().enqueuePotentiallyPrivateEvent(
- EVENTKEYS_POINTERTRACKER_CALLLISTENERONCODEINPUT, values);
+ getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCODEINPUT,
+ Constants.printableCode(scrubDigitFromCodePoint(code)),
+ outputText == null ? null : scrubDigitsFromString(outputText.toString()),
+ x, y, ignoreModifierKey, altersCode, key.isEnabled());
}
}
- private static final String[] EVENTKEYS_POINTERTRACKER_CALLLISTENERONRELEASE = {
- "PointerTrackerCallListenerOnRelease", "code", "withSliding", "ignoreModifierKey",
- "isEnabled"
- };
+ /**
+ * Log a call to PointerTracker.callListenerCallListenerOnRelease().
+ *
+ * UserAction: The user has released their finger or thumb from the screen.
+ */
+ private static final LogStatement LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONRELEASE =
+ new LogStatement("PointerTrackerCallListenerOnRelease", true, false, "code",
+ "withSliding", "ignoreModifierKey", "isEnabled");
public static void pointerTracker_callListenerOnRelease(final Key key, final int primaryCode,
final boolean withSliding, final boolean ignoreModifierKey) {
if (key != null) {
- final Object[] values = {
- Keyboard.printableCode(scrubDigitFromCodePoint(primaryCode)), withSliding,
- ignoreModifierKey, key.isEnabled()
- };
- getInstance().enqueuePotentiallyPrivateEvent(
- EVENTKEYS_POINTERTRACKER_CALLLISTENERONRELEASE, values);
+ getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONRELEASE,
+ Constants.printableCode(scrubDigitFromCodePoint(primaryCode)), withSliding,
+ ignoreModifierKey, key.isEnabled());
}
}
- private static final String[] EVENTKEYS_POINTERTRACKER_ONDOWNEVENT = {
- "PointerTrackerOnDownEvent", "deltaT", "distanceSquared"
- };
+ /**
+ * Log a call to PointerTracker.onDownEvent().
+ *
+ * UserAction: The user has pressed down on a key.
+ * TODO: Differentiate with LatinIME.processMotionEvent.
+ */
+ private static final LogStatement LOGSTATEMENT_POINTERTRACKER_ONDOWNEVENT =
+ new LogStatement("PointerTrackerOnDownEvent", true, false, "deltaT", "distanceSquared");
public static void pointerTracker_onDownEvent(long deltaT, int distanceSquared) {
- final Object[] values = {
- deltaT, distanceSquared
- };
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_POINTERTRACKER_ONDOWNEVENT, values);
+ getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_ONDOWNEVENT, deltaT,
+ distanceSquared);
}
- private static final String[] EVENTKEYS_POINTERTRACKER_ONMOVEEVENT = {
- "PointerTrackerOnMoveEvent", "x", "y", "lastX", "lastY"
- };
+ /**
+ * Log a call to PointerTracker.onMoveEvent().
+ *
+ * UserAction: The user has moved their finger while pressing on the screen.
+ * TODO: Differentiate with LatinIME.processMotionEvent().
+ */
+ private static final LogStatement LOGSTATEMENT_POINTERTRACKER_ONMOVEEVENT =
+ new LogStatement("PointerTrackerOnMoveEvent", true, false, "x", "y", "lastX", "lastY");
public static void pointerTracker_onMoveEvent(final int x, final int y, final int lastX,
final int lastY) {
- final Object[] values = {
- x, y, lastX, lastY
- };
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_POINTERTRACKER_ONMOVEEVENT, values);
+ getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_ONMOVEEVENT, x, y, lastX, lastY);
}
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITCOMPLETION = {
- "RichInputConnectionCommitCompletion", "completionInfo"
- };
+ /**
+ * Log a call to RichInputConnection.commitCompletion().
+ *
+ * SystemResponse: The IME has committed a completion. A completion is an application-
+ * specific suggestion that is presented in a pop-up menu in the TextView.
+ */
+ private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_COMMITCOMPLETION =
+ new LogStatement("RichInputConnectionCommitCompletion", true, false, "completionInfo");
public static void richInputConnection_commitCompletion(final CompletionInfo completionInfo) {
- final Object[] values = {
- completionInfo
- };
final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(
- EVENTKEYS_RICHINPUTCONNECTION_COMMITCOMPLETION, values);
- }
-
- // Disabled for privacy-protection reasons. Because this event comes after
- // richInputConnection_commitText, which is the event used to separate LogUnits, the
- // data in this event can be associated with the next LogUnit, revealing information
- // about the current word even if it was supposed to be suppressed. The occurrance of
- // autocorrection can be determined by examining the difference between the text strings in
- // the last call to richInputConnection_setComposingText before
- // richInputConnection_commitText, so it's not a data loss.
- // TODO: Figure out how to log this event without loss of privacy.
- /*
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITCORRECTION = {
- "RichInputConnectionCommitCorrection", "typedWord", "autoCorrection"
- };
- */
- public static void richInputConnection_commitCorrection(CorrectionInfo correctionInfo) {
- /*
- final String typedWord = correctionInfo.getOldText().toString();
- final String autoCorrection = correctionInfo.getNewText().toString();
- final Object[] values = {
- scrubDigitsFromString(typedWord), scrubDigitsFromString(autoCorrection)
- };
+ researchLogger.enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_COMMITCOMPLETION,
+ completionInfo);
+ }
+
+ /**
+ * Log a call to LatinIME.commitCurrentAutoCorrection().
+ *
+ * SystemResponse: The IME has committed an auto-correction. An auto-correction changes the raw
+ * text input to another word that the user more likely desired to type.
+ */
+ private static final LogStatement LOGSTATEMENT_LATINIME_COMMITCURRENTAUTOCORRECTION =
+ new LogStatement("LatinIMECommitCurrentAutoCorrection", true, false, "typedWord",
+ "autoCorrection", "separatorString");
+ public static void latinIme_commitCurrentAutoCorrection(final String typedWord,
+ final String autoCorrection, final String separatorString) {
+ final String scrubbedTypedWord = scrubDigitsFromString(typedWord);
+ final String scrubbedAutoCorrection = scrubDigitsFromString(autoCorrection);
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_COMMITCURRENTAUTOCORRECTION,
+ scrubbedTypedWord, scrubbedAutoCorrection, separatorString);
+ researchLogger.onWordComplete(scrubbedAutoCorrection, Long.MAX_VALUE);
+ }
+
+ private boolean isExpectingCommitText = false;
+ /**
+ * Log a call to RichInputConnection.commitPartialText
+ *
+ * SystemResponse: The IME is committing part of a word. This happens if a space is
+ * automatically inserted to split a single typed string into two or more words.
+ */
+ // TODO: This method is currently unused. Find where it should be called from in the IME and
+ // add invocations.
+ private static final LogStatement LOGSTATEMENT_LATINIME_COMMIT_PARTIAL_TEXT =
+ new LogStatement("LatinIMECommitPartialText", true, false, "newCursorPosition");
+ public static void latinIME_commitPartialText(final CharSequence committedWord,
+ final long lastTimestampOfWordData) {
final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(
- EVENTKEYS_RICHINPUTCONNECTION_COMMITCORRECTION, values);
- */
+ final String scrubbedWord = scrubDigitsFromString(committedWord.toString());
+ researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_COMMIT_PARTIAL_TEXT);
+ researchLogger.onWordComplete(scrubbedWord, lastTimestampOfWordData);
+ researchLogger.mStatistics.recordSplitWords();
}
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITTEXT = {
- "RichInputConnectionCommitText", "typedWord", "newCursorPosition"
- };
- public static void richInputConnection_commitText(final CharSequence typedWord,
+ /**
+ * Log a call to RichInputConnection.commitText().
+ *
+ * SystemResponse: The IME is committing text. This happens after the user has typed a word
+ * and then a space or punctuation key.
+ */
+ private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTIONCOMMITTEXT =
+ new LogStatement("RichInputConnectionCommitText", true, false, "newCursorPosition");
+ public static void richInputConnection_commitText(final CharSequence committedWord,
final int newCursorPosition) {
- final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
- final Object[] values = {
- scrubbedWord, newCursorPosition
- };
final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_COMMITTEXT,
- values);
- researchLogger.onWordComplete(scrubbedWord);
+ final String scrubbedWord = scrubDigitsFromString(committedWord.toString());
+ if (!researchLogger.isExpectingCommitText) {
+ researchLogger.enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTIONCOMMITTEXT,
+ newCursorPosition);
+ researchLogger.onWordComplete(scrubbedWord, Long.MAX_VALUE);
+ }
+ researchLogger.isExpectingCommitText = false;
}
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT = {
- "RichInputConnectionDeleteSurroundingText", "beforeLength", "afterLength"
- };
+ /**
+ * Shared event for logging committed text.
+ */
+ private static final LogStatement LOGSTATEMENT_COMMITTEXT =
+ new LogStatement("CommitText", true, false, "committedText");
+ private void enqueueCommitText(final CharSequence word) {
+ enqueueEvent(LOGSTATEMENT_COMMITTEXT, word);
+ }
+
+ /**
+ * Log a call to RichInputConnection.deleteSurroundingText().
+ *
+ * SystemResponse: The IME has deleted text.
+ */
+ private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT =
+ new LogStatement("RichInputConnectionDeleteSurroundingText", true, false,
+ "beforeLength", "afterLength");
public static void richInputConnection_deleteSurroundingText(final int beforeLength,
final int afterLength) {
- final Object[] values = {
- beforeLength, afterLength
- };
- getInstance().enqueuePotentiallyPrivateEvent(
- EVENTKEYS_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT, values);
+ getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT,
+ beforeLength, afterLength);
}
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT = {
- "RichInputConnectionFinishComposingText"
- };
+ /**
+ * Log a call to RichInputConnection.finishComposingText().
+ *
+ * SystemResponse: The IME has left the composing text as-is.
+ */
+ private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT =
+ new LogStatement("RichInputConnectionFinishComposingText", false, false);
public static void richInputConnection_finishComposingText() {
- getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT,
- EVENTKEYS_NULLVALUES);
+ getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT);
}
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_PERFORMEDITORACTION = {
- "RichInputConnectionPerformEditorAction", "imeActionNext"
- };
- public static void richInputConnection_performEditorAction(final int imeActionNext) {
- final Object[] values = {
- imeActionNext
- };
- getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_PERFORMEDITORACTION, values);
+ /**
+ * Log a call to RichInputConnection.performEditorAction().
+ *
+ * SystemResponse: The IME is invoking an action specific to the editor.
+ */
+ private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_PERFORMEDITORACTION =
+ new LogStatement("RichInputConnectionPerformEditorAction", false, false,
+ "imeActionId");
+ public static void richInputConnection_performEditorAction(final int imeActionId) {
+ getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_PERFORMEDITORACTION,
+ imeActionId);
}
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SENDKEYEVENT = {
- "RichInputConnectionSendKeyEvent", "eventTime", "action", "code"
- };
+ /**
+ * Log a call to RichInputConnection.sendKeyEvent().
+ *
+ * SystemResponse: The IME is telling the TextView that a key is being pressed through an
+ * alternate channel.
+ * TODO: only for hardware keys?
+ */
+ private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_SENDKEYEVENT =
+ new LogStatement("RichInputConnectionSendKeyEvent", true, false, "eventTime", "action",
+ "code");
public static void richInputConnection_sendKeyEvent(final KeyEvent keyEvent) {
- final Object[] values = {
- keyEvent.getEventTime(),
- keyEvent.getAction(),
- keyEvent.getKeyCode()
- };
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_SENDKEYEVENT,
- values);
+ getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_SENDKEYEVENT,
+ keyEvent.getEventTime(), keyEvent.getAction(), keyEvent.getKeyCode());
}
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SETCOMPOSINGTEXT = {
- "RichInputConnectionSetComposingText", "text", "newCursorPosition"
- };
+ /**
+ * Log a call to RichInputConnection.setComposingText().
+ *
+ * SystemResponse: The IME is setting the composing text. Happens each time a character is
+ * entered.
+ */
+ private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_SETCOMPOSINGTEXT =
+ new LogStatement("RichInputConnectionSetComposingText", true, true, "text",
+ "newCursorPosition");
public static void richInputConnection_setComposingText(final CharSequence text,
final int newCursorPosition) {
if (text == null) {
throw new RuntimeException("setComposingText is null");
}
- final Object[] values = {
- text, newCursorPosition
- };
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_SETCOMPOSINGTEXT,
- values);
+ getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_SETCOMPOSINGTEXT, text,
+ newCursorPosition);
}
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SETSELECTION = {
- "RichInputConnectionSetSelection", "from", "to"
- };
+ /**
+ * Log a call to RichInputConnection.setSelection().
+ *
+ * SystemResponse: The IME is requesting that the selection change. User-initiated selection-
+ * change requests do not go through this method -- it's only when the system wants to change
+ * the selection.
+ */
+ private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_SETSELECTION =
+ new LogStatement("RichInputConnectionSetSelection", true, false, "from", "to");
public static void richInputConnection_setSelection(final int from, final int to) {
- final Object[] values = {
- from, to
- };
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_SETSELECTION,
- values);
+ getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_SETSELECTION, from, to);
}
- private static final String[] EVENTKEYS_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT = {
- "SuddenJumpingTouchEventHandlerOnTouchEvent", "motionEvent"
- };
+ /**
+ * Log a call to SuddenJumpingTouchEventHandler.onTouchEvent().
+ *
+ * SystemResponse: The IME has filtered input events in case of an erroneous sensor reading.
+ */
+ private static final LogStatement LOGSTATEMENT_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT =
+ new LogStatement("SuddenJumpingTouchEventHandlerOnTouchEvent", true, false,
+ "motionEvent");
public static void suddenJumpingTouchEventHandler_onTouchEvent(final MotionEvent me) {
if (me != null) {
- final Object[] values = {
- me.toString()
- };
- getInstance().enqueuePotentiallyPrivateEvent(
- EVENTKEYS_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT, values);
+ getInstance().enqueueEvent(LOGSTATEMENT_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT,
+ me.toString());
}
}
- private static final String[] EVENTKEYS_SUGGESTIONSTRIPVIEW_SETSUGGESTIONS = {
- "SuggestionStripViewSetSuggestions", "suggestedWords"
- };
+ /**
+ * Log a call to SuggestionsView.setSuggestions().
+ *
+ * SystemResponse: The IME is setting the suggestions in the suggestion strip.
+ */
+ private static final LogStatement LOGSTATEMENT_SUGGESTIONSTRIPVIEW_SETSUGGESTIONS =
+ new LogStatement("SuggestionStripViewSetSuggestions", true, true, "suggestedWords");
public static void suggestionStripView_setSuggestions(final SuggestedWords suggestedWords) {
if (suggestedWords != null) {
- final Object[] values = {
- suggestedWords
- };
- getInstance().enqueuePotentiallyPrivateEvent(
- EVENTKEYS_SUGGESTIONSTRIPVIEW_SETSUGGESTIONS, values);
+ getInstance().enqueueEvent(LOGSTATEMENT_SUGGESTIONSTRIPVIEW_SETSUGGESTIONS,
+ suggestedWords);
}
}
- private static final String[] EVENTKEYS_USER_TIMESTAMP = {
- "UserTimestamp"
- };
+ /**
+ * The user has indicated a particular point in the log that is of interest.
+ *
+ * UserAction: From direct menu invocation.
+ */
+ private static final LogStatement LOGSTATEMENT_USER_TIMESTAMP =
+ new LogStatement("UserTimestamp", false, false);
public void userTimestamp() {
- getInstance().enqueueEvent(EVENTKEYS_USER_TIMESTAMP, EVENTKEYS_NULLVALUES);
+ getInstance().enqueueEvent(LOGSTATEMENT_USER_TIMESTAMP);
+ }
+
+ /**
+ * Log a call to LatinIME.onEndBatchInput().
+ *
+ * SystemResponse: The system has completed a gesture.
+ */
+ private static final LogStatement LOGSTATEMENT_LATINIME_ONENDBATCHINPUT =
+ new LogStatement("LatinIMEOnEndBatchInput", true, false, "enteredText",
+ "enteredWordPos");
+ public static void latinIME_onEndBatchInput(final CharSequence enteredText,
+ final int enteredWordPos) {
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONENDBATCHINPUT, enteredText,
+ enteredWordPos);
+ researchLogger.mStatistics.recordGestureInput(enteredText.length());
}
- private static final String[] EVENTKEYS_STATISTICS = {
- "Statistics", "charCount", "letterCount", "numberCount", "spaceCount", "deleteOpsCount",
- "wordCount", "isEmptyUponStarting", "isEmptinessStateKnown", "averageTimeBetweenKeys",
- "averageTimeBeforeDelete", "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete"
- };
+ /**
+ * Log a call to LatinIME.handleBackspace().
+ *
+ * UserInput: The user is deleting a gestured word by hitting the backspace key once.
+ */
+ private static final LogStatement LOGSTATEMENT_LATINIME_HANDLEBACKSPACE_BATCH =
+ new LogStatement("LatinIMEHandleBackspaceBatch", true, false, "deletedText");
+ public static void latinIME_handleBackspace_batch(final CharSequence deletedText) {
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLEBACKSPACE_BATCH, deletedText);
+ researchLogger.mStatistics.recordGestureDelete();
+ }
+
+ /**
+ * Log statistics.
+ *
+ * ContextualData, recorded at the end of a session.
+ */
+ private static final LogStatement LOGSTATEMENT_STATISTICS =
+ new LogStatement("Statistics", false, false, "charCount", "letterCount", "numberCount",
+ "spaceCount", "deleteOpsCount", "wordCount", "isEmptyUponStarting",
+ "isEmptinessStateKnown", "averageTimeBetweenKeys", "averageTimeBeforeDelete",
+ "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete",
+ "dictionaryWordCount", "splitWordsCount", "gestureInputCount",
+ "gestureCharsCount", "gesturesDeletedCount", "manualSuggestionsCount",
+ "revertCommitsCount");
private static void logStatistics() {
final ResearchLogger researchLogger = getInstance();
final Statistics statistics = researchLogger.mStatistics;
- final Object[] values = {
- statistics.mCharCount, statistics.mLetterCount, statistics.mNumberCount,
- statistics.mSpaceCount, statistics.mDeleteKeyCount,
- statistics.mWordCount, statistics.mIsEmptyUponStarting,
- statistics.mIsEmptinessStateKnown, statistics.mKeyCounter.getAverageTime(),
- statistics.mBeforeDeleteKeyCounter.getAverageTime(),
- statistics.mDuringRepeatedDeleteKeysCounter.getAverageTime(),
- statistics.mAfterDeleteKeyCounter.getAverageTime()
- };
- researchLogger.enqueueEvent(EVENTKEYS_STATISTICS, values);
+ researchLogger.enqueueEvent(LOGSTATEMENT_STATISTICS, statistics.mCharCount,
+ statistics.mLetterCount, statistics.mNumberCount, statistics.mSpaceCount,
+ statistics.mDeleteKeyCount, statistics.mWordCount, statistics.mIsEmptyUponStarting,
+ statistics.mIsEmptinessStateKnown, statistics.mKeyCounter.getAverageTime(),
+ statistics.mBeforeDeleteKeyCounter.getAverageTime(),
+ statistics.mDuringRepeatedDeleteKeysCounter.getAverageTime(),
+ statistics.mAfterDeleteKeyCounter.getAverageTime(),
+ statistics.mDictionaryWordCount, statistics.mSplitWordsCount,
+ statistics.mGesturesInputCount, statistics.mGesturesCharsCount,
+ statistics.mGesturesDeletedCount, statistics.mManualSuggestionsCount,
+ statistics.mRevertCommitsCount);
}
}
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
index eab465aa2..e9c02c919 100644
--- a/java/src/com/android/inputmethod/research/Statistics.java
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -16,9 +16,15 @@
package com.android.inputmethod.research;
-import com.android.inputmethod.keyboard.Keyboard;
+import android.util.Log;
+
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.define.ProductionFlag;
public class Statistics {
+ private static final String TAG = Statistics.class.getSimpleName();
+ private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
+
// Number of characters entered during a typing session
int mCharCount;
// Number of letter characters entered during a typing session
@@ -31,6 +37,20 @@ public class Statistics {
int mDeleteKeyCount;
// Number of words entered during a session.
int mWordCount;
+ // Number of words found in the dictionary.
+ int mDictionaryWordCount;
+ // Number of words split and spaces automatically entered.
+ int mSplitWordsCount;
+ // Number of gestures that were input.
+ int mGesturesInputCount;
+ // Number of gestures that were deleted.
+ int mGesturesDeletedCount;
+ // Total number of characters in words entered by gesture.
+ int mGesturesCharsCount;
+ // Number of manual suggestions chosen.
+ int mManualSuggestionsCount;
+ // Number of times a commit was reverted in this session.
+ int mRevertCommitsCount;
// Whether the text field was empty upon editing
boolean mIsEmptyUponStarting;
boolean mIsEmptinessStateKnown;
@@ -91,20 +111,31 @@ public class Statistics {
mSpaceCount = 0;
mDeleteKeyCount = 0;
mWordCount = 0;
+ mDictionaryWordCount = 0;
+ mSplitWordsCount = 0;
+ mGesturesInputCount = 0;
+ mGesturesDeletedCount = 0;
+ mManualSuggestionsCount = 0;
+ mRevertCommitsCount = 0;
mIsEmptyUponStarting = true;
mIsEmptinessStateKnown = false;
mKeyCounter.reset();
mBeforeDeleteKeyCounter.reset();
mDuringRepeatedDeleteKeysCounter.reset();
mAfterDeleteKeyCounter.reset();
+ mGesturesCharsCount = 0;
+ mGesturesDeletedCount = 0;
mLastTapTime = 0;
mIsLastKeyDeleteKey = false;
}
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) {
@@ -135,12 +166,35 @@ public class Statistics {
mLastTapTime = time;
}
- public void recordWordEntered() {
+ public void recordWordEntered(final boolean isDictionaryWord) {
mWordCount++;
+ if (isDictionaryWord) {
+ mDictionaryWordCount++;
+ }
+ }
+
+ public void recordSplitWords() {
+ mSplitWordsCount++;
+ }
+
+ public void recordGestureInput(final int numCharsEntered) {
+ mGesturesInputCount++;
+ mGesturesCharsCount += numCharsEntered;
}
public void setIsEmptyUponStarting(final boolean isEmpty) {
mIsEmptyUponStarting = isEmpty;
mIsEmptinessStateKnown = true;
}
+
+ public void recordGestureDelete() {
+ mGesturesDeletedCount++;
+ }
+ public void recordManualSuggestion() {
+ mManualSuggestionsCount++;
+ }
+
+ public void recordRevertCommit() {
+ mRevertCommitsCount++;
+ }
}
diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java
index 7a5749096..a1ecc1118 100644
--- a/java/src/com/android/inputmethod/research/UploaderService.java
+++ b/java/src/com/android/inputmethod/research/UploaderService.java
@@ -30,6 +30,7 @@ import android.os.Bundle;
import android.util.Log;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.BufferedReader;
import java.io.File;
@@ -45,6 +46,10 @@ import java.net.URL;
public final class UploaderService extends IntentService {
private static final String TAG = UploaderService.class.getSimpleName();
+ private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
+ // Set IS_INHIBITING_AUTO_UPLOAD to true for local testing
+ private static final boolean IS_INHIBITING_AUTO_UPLOAD = false
+ && ProductionFlag.IS_EXPERIMENTAL_DEBUG; // Force false in production
public static final long RUN_INTERVAL = AlarmManager.INTERVAL_HOUR;
private static final String EXTRA_UPLOAD_UNCONDITIONALLY = UploaderService.class.getName()
+ ".extra.UPLOAD_UNCONDITIONALLY";
@@ -116,7 +121,8 @@ public final class UploaderService extends IntentService {
}
private void doUpload(final boolean isUploadingUnconditionally) {
- if (!isUploadingUnconditionally && (!isExternallyPowered() || !hasWifiConnection())) {
+ if (!isUploadingUnconditionally && (!isExternallyPowered() || !hasWifiConnection()
+ || IS_INHIBITING_AUTO_UPLOAD)) {
return;
}
if (mFilesDir == null) {
@@ -141,7 +147,9 @@ public final class UploaderService extends IntentService {
}
private boolean uploadFile(File file) {
- Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
+ if (DEBUG) {
+ Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
+ }
boolean success = false;
final int contentLength = (int) file.length();
HttpURLConnection connection = null;
@@ -157,6 +165,9 @@ public final class UploaderService extends IntentService {
int numBytesRead;
while ((numBytesRead = fileInputStream.read(buf)) != -1) {
os.write(buf, 0, numBytesRead);
+ if (DEBUG) {
+ Log.d(TAG, new String(buf));
+ }
}
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
Log.d(TAG, "upload failed: " + connection.getResponseCode());
@@ -171,7 +182,9 @@ public final class UploaderService extends IntentService {
}
file.delete();
success = true;
- Log.d(TAG, "upload successful");
+ if (DEBUG) {
+ Log.d(TAG, "upload successful");
+ }
} catch (Exception e) {
e.printStackTrace();
} finally {