ResearchLogger: make logging more reliable (esp on startup)

Bug: 6188932
Change-Id: I692e427ba2e6da7bb15f48208304c4a034392a22
main
Kurt Partridge 2012-07-18 13:52:41 -07:00
parent 9370ab9ada
commit 0df487678e
5 changed files with 197 additions and 107 deletions

View File

@ -223,15 +223,15 @@
<string name="notify_recorded_timestamp">Recorded timestamp</string> <string name="notify_recorded_timestamp">Recorded timestamp</string>
<!-- Title for dialog option to let users cancel logging and delete log for this session [CHAR LIMIT=35] --> <!-- Title for dialog option to let users cancel logging and delete log for this session [CHAR LIMIT=35] -->
<string name="do_not_log_this_session">Do not log this session</string> <string name="do_not_log_this_session">Suspend logging</string>
<!-- Title for dialog option to let users reenable logging [CHAR LIMIT=35] --> <!-- Title for dialog option to let users reenable logging [CHAR LIMIT=35] -->
<string name="enable_session_logging">Enable session logging</string> <string name="enable_session_logging">Enable logging</string>
<!-- Title for dialog option to let users log all events in this session [CHAR LIMIT=35] --> <!-- Title for dialog option to let users log all events in this session [CHAR LIMIT=35] -->
<string name="log_whole_session_history">Log whole session history</string> <string name="log_whole_session_history">Log whole session history</string>
<!-- Toast notification that the system is processing the request to delete the log for this session [CHAR LIMIT=35] --> <!-- Toast notification that the system is processing the request to delete the log for this session [CHAR LIMIT=35] -->
<string name="notify_session_log_deleting">Deleting session log</string> <string name="notify_session_log_deleting">Deleting session log</string>
<!-- Toast notification that the system has successfully deleted the log for this session [CHAR LIMIT=35] --> <!-- Toast notification that the system has successfully deleted the log for this session [CHAR LIMIT=35] -->
<string name="notify_session_log_deleted">Session log deleted</string> <string name="notify_logging_suspended">Logging temporarily suspended. To disable permanently, go to Android Keyboard Settings</string>
<!-- Toast notification that the system has failed to delete the log for this session [CHAR LIMIT=35] --> <!-- Toast notification that the system has failed to delete the log for this session [CHAR LIMIT=35] -->
<string name="notify_session_log_not_deleted">Session log NOT deleted</string> <string name="notify_session_log_not_deleted">Session log NOT deleted</string>
<!-- Toast notification that the system has recorded the whole session history [CHAR LIMIT=35] --> <!-- Toast notification that the system has recorded the whole session history [CHAR LIMIT=35] -->
@ -240,7 +240,6 @@
<string name="notify_session_history_not_logged">Error: Session history NOT logged</string> <string name="notify_session_history_not_logged">Error: Session history NOT logged</string>
<!-- Toast notification that the system is enabling logging [CHAR LIMIT=35] --> <!-- Toast notification that the system is enabling logging [CHAR LIMIT=35] -->
<string name="notify_session_logging_enabled">Session logging enabled</string> <string name="notify_session_logging_enabled">Session logging enabled</string>
<!-- Preference for input language selection --> <!-- Preference for input language selection -->
<string name="select_language">Input languages</string> <string name="select_language">Input languages</string>

View File

@ -618,7 +618,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
+ ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0)); + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0));
} }
if (ProductionFlag.IS_EXPERIMENTAL) { if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.getInstance().start();
ResearchLogger.latinIME_onStartInputViewInternal(editorInfo, mPrefs); ResearchLogger.latinIME_onStartInputViewInternal(editorInfo, mPrefs);
} }
if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) { if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) {
@ -711,7 +710,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
LatinImeLogger.commit(); LatinImeLogger.commit();
if (ProductionFlag.IS_EXPERIMENTAL) { if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.getInstance().stop(); ResearchLogger.getInstance().latinIME_onFinishInputInternal();
} }
KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();

View File

@ -55,13 +55,14 @@ public class ResearchLog {
final ScheduledExecutorService mExecutor; final ScheduledExecutorService mExecutor;
/* package */ final File mFile; /* package */ final File mFile;
private JsonWriter mJsonWriter = NULL_JSON_WRITER; // should never be null private JsonWriter mJsonWriter = NULL_JSON_WRITER;
private int mLoggingState; private int mLoggingState;
private static final int LOGGING_STATE_UNSTARTED = 0; private static final int LOGGING_STATE_UNSTARTED = 0;
private static final int LOGGING_STATE_RUNNING = 1; private static final int LOGGING_STATE_READY = 1; // don't create file until necessary
private static final int LOGGING_STATE_STOPPING = 2; private static final int LOGGING_STATE_RUNNING = 2;
private static final int LOGGING_STATE_STOPPED = 3; private static final int LOGGING_STATE_STOPPING = 3;
private static final int LOGGING_STATE_STOPPED = 4;
private static final long FLUSH_DELAY_IN_MS = 1000 * 5; private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
private static class NullOutputStream extends OutputStream { private static class NullOutputStream extends OutputStream {
@ -94,11 +95,9 @@ public class ResearchLog {
public synchronized void start() throws IOException { public synchronized void start() throws IOException {
switch (mLoggingState) { switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED: case LOGGING_STATE_UNSTARTED:
mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile))); mLoggingState = LOGGING_STATE_READY;
mJsonWriter.setLenient(true);
mJsonWriter.beginArray();
mLoggingState = LOGGING_STATE_RUNNING;
break; break;
case LOGGING_STATE_READY:
case LOGGING_STATE_RUNNING: case LOGGING_STATE_RUNNING:
case LOGGING_STATE_STOPPING: case LOGGING_STATE_STOPPING:
case LOGGING_STATE_STOPPED: case LOGGING_STATE_STOPPED:
@ -111,6 +110,7 @@ public class ResearchLog {
case LOGGING_STATE_UNSTARTED: case LOGGING_STATE_UNSTARTED:
mLoggingState = LOGGING_STATE_STOPPED; mLoggingState = LOGGING_STATE_STOPPED;
break; break;
case LOGGING_STATE_READY:
case LOGGING_STATE_RUNNING: case LOGGING_STATE_RUNNING:
mExecutor.submit(new Callable<Object>() { mExecutor.submit(new Callable<Object>() {
@Override @Override
@ -120,14 +120,13 @@ public class ResearchLog {
mJsonWriter.flush(); mJsonWriter.flush();
mJsonWriter.close(); mJsonWriter.close();
} finally { } finally {
// the contentprovider only exports data if the writable
// bit is cleared.
boolean success = mFile.setWritable(false, false); boolean success = mFile.setWritable(false, false);
mLoggingState = LOGGING_STATE_STOPPED; mLoggingState = LOGGING_STATE_STOPPED;
} }
return null; return null;
} }
}); });
removeAnyScheduledFlush();
mExecutor.shutdown(); mExecutor.shutdown();
mLoggingState = LOGGING_STATE_STOPPING; mLoggingState = LOGGING_STATE_STOPPING;
break; break;
@ -139,27 +138,26 @@ public class ResearchLog {
public boolean isAlive() { public boolean isAlive() {
switch (mLoggingState) { switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED: case LOGGING_STATE_UNSTARTED:
case LOGGING_STATE_READY:
case LOGGING_STATE_RUNNING: case LOGGING_STATE_RUNNING:
return true; return true;
} }
return false; return false;
} }
public void waitUntilStopped(int timeoutInMs) throws InterruptedException { public void waitUntilStopped(final int timeoutInMs) throws InterruptedException {
removeAnyScheduledFlush();
mExecutor.shutdown();
mExecutor.awaitTermination(timeoutInMs, TimeUnit.MILLISECONDS); mExecutor.awaitTermination(timeoutInMs, TimeUnit.MILLISECONDS);
} }
private boolean isAbortSuccessful;
public boolean isAbortSuccessful() {
return isAbortSuccessful;
}
public synchronized void abort() { public synchronized void abort() {
switch (mLoggingState) { switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED: case LOGGING_STATE_UNSTARTED:
mLoggingState = LOGGING_STATE_STOPPED; mLoggingState = LOGGING_STATE_STOPPED;
isAbortSuccessful = true; isAbortSuccessful = true;
break; break;
case LOGGING_STATE_READY:
case LOGGING_STATE_RUNNING: case LOGGING_STATE_RUNNING:
mExecutor.submit(new Callable<Object>() { mExecutor.submit(new Callable<Object>() {
@Override @Override
@ -173,6 +171,7 @@ public class ResearchLog {
return null; return null;
} }
}); });
removeAnyScheduledFlush();
mExecutor.shutdown(); mExecutor.shutdown();
mLoggingState = LOGGING_STATE_STOPPING; mLoggingState = LOGGING_STATE_STOPPING;
break; break;
@ -181,10 +180,16 @@ public class ResearchLog {
} }
} }
private boolean isAbortSuccessful;
public boolean isAbortSuccessful() {
return isAbortSuccessful;
}
/* package */ synchronized void flush() { /* package */ synchronized void flush() {
switch (mLoggingState) { switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED: case LOGGING_STATE_UNSTARTED:
break; break;
case LOGGING_STATE_READY:
case LOGGING_STATE_RUNNING: case LOGGING_STATE_RUNNING:
removeAnyScheduledFlush(); removeAnyScheduledFlush();
mExecutor.submit(mFlushCallable); mExecutor.submit(mFlushCallable);
@ -197,7 +202,9 @@ public class ResearchLog {
private Callable<Object> mFlushCallable = new Callable<Object>() { private Callable<Object> mFlushCallable = new Callable<Object>() {
@Override @Override
public Object call() throws Exception { public Object call() throws Exception {
mJsonWriter.flush(); if (mLoggingState == LOGGING_STATE_RUNNING) {
mJsonWriter.flush();
}
return null; return null;
} }
}; };
@ -220,6 +227,7 @@ public class ResearchLog {
switch (mLoggingState) { switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED: case LOGGING_STATE_UNSTARTED:
break; break;
case LOGGING_STATE_READY:
case LOGGING_STATE_RUNNING: case LOGGING_STATE_RUNNING:
mExecutor.submit(new Callable<Object>() { mExecutor.submit(new Callable<Object>() {
@Override @Override
@ -239,6 +247,7 @@ public class ResearchLog {
switch (mLoggingState) { switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED: case LOGGING_STATE_UNSTARTED:
break; break;
case LOGGING_STATE_READY:
case LOGGING_STATE_RUNNING: case LOGGING_STATE_RUNNING:
mExecutor.submit(new Callable<Object>() { mExecutor.submit(new Callable<Object>() {
@Override @Override
@ -260,6 +269,11 @@ public class ResearchLog {
void outputEvent(final String[] keys, final Object[] values) { void outputEvent(final String[] keys, final Object[] values) {
// not thread safe. // not thread safe.
try { try {
if (mJsonWriter == NULL_JSON_WRITER) {
mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile)));
mJsonWriter.setLenient(true);
mJsonWriter.beginArray();
}
mJsonWriter.beginObject(); mJsonWriter.beginObject();
mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis()); mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
mJsonWriter.name(UPTIME_KEY).value(SystemClock.uptimeMillis()); mJsonWriter.name(UPTIME_KEY).value(SystemClock.uptimeMillis());

View File

@ -44,7 +44,6 @@ import com.android.inputmethod.latin.RichInputConnection.Range;
import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.File; import java.io.File;
import java.io.FileFilter;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
@ -64,13 +63,15 @@ import java.util.UUID;
public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener { public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = ResearchLogger.class.getSimpleName(); private static final String TAG = ResearchLogger.class.getSimpleName();
private static final boolean OUTPUT_ENTIRE_BUFFER = false; // true may disclose private info private static final boolean OUTPUT_ENTIRE_BUFFER = false; // true may disclose private info
/* package */ static final boolean DEFAULT_USABILITY_STUDY_MODE = false;
/* package */ static boolean sIsLogging = false; /* package */ static boolean sIsLogging = false;
private static final int OUTPUT_FORMAT_VERSION = 1; private static final int OUTPUT_FORMAT_VERSION = 1;
private static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode"; private static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
private static final String FILENAME_PREFIX = "researchLog"; /* package */ static final String FILENAME_PREFIX = "researchLog";
private static final String FILENAME_SUFFIX = ".txt"; private static final String FILENAME_SUFFIX = ".txt";
private static final SimpleDateFormat TIMESTAMP_DATEFORMAT = private static final SimpleDateFormat TIMESTAMP_DATEFORMAT =
new SimpleDateFormat("yyyyMMddHHmmssS", Locale.US); new SimpleDateFormat("yyyyMMddHHmmssS", Locale.US);
private static final boolean IS_SHOWING_INDICATOR = false;
// constants related to specific log points // constants related to specific log points
private static final String WHITESPACE_SEPARATORS = " \t\n\r"; private static final String WHITESPACE_SEPARATORS = " \t\n\r";
@ -92,6 +93,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private boolean mIsPasswordView = false; private boolean mIsPasswordView = false;
private boolean mIsLoggingSuspended = false; private boolean mIsLoggingSuspended = false;
private SharedPreferences mPrefs;
// digits entered by the user are replaced with this codepoint. // digits entered by the user are replaced with this codepoint.
/* package for test */ static final int DIGIT_REPLACEMENT_CODEPOINT = /* package for test */ static final int DIGIT_REPLACEMENT_CODEPOINT =
@ -101,6 +103,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private static final String PREF_LAST_CLEANUP_TIME = "pref_last_cleanup_time"; private static final String PREF_LAST_CLEANUP_TIME = "pref_last_cleanup_time";
private static final long DURATION_BETWEEN_DIR_CLEANUP_IN_MS = DateUtils.DAY_IN_MILLIS; private static final long DURATION_BETWEEN_DIR_CLEANUP_IN_MS = DateUtils.DAY_IN_MILLIS;
private static final long MAX_LOGFILE_AGE_IN_MS = DateUtils.DAY_IN_MILLIS; private static final long MAX_LOGFILE_AGE_IN_MS = DateUtils.DAY_IN_MILLIS;
protected static final int SUSPEND_DURATION_IN_MINUTES = 1;
// set when LatinIME should ignore an onUpdateSelection() callback that // set when LatinIME should ignore an onUpdateSelection() callback that
// arises from operations in this class // arises from operations in this class
private static boolean sLatinIMEExpectingUpdateSelection = false; private static boolean sLatinIMEExpectingUpdateSelection = false;
@ -124,7 +127,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (ims == null) { if (ims == null) {
Log.w(TAG, "IMS is null; logging is off"); Log.w(TAG, "IMS is null; logging is off");
} else { } else {
mContext = ims;
mFilesDir = ims.getFilesDir(); mFilesDir = ims.getFilesDir();
if (mFilesDir == null || !mFilesDir.exists()) { if (mFilesDir == null || !mFilesDir.exists()) {
Log.w(TAG, "IME storage directory does not exist."); Log.w(TAG, "IME storage directory does not exist.");
@ -132,6 +134,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
} }
if (prefs != null) { if (prefs != null) {
mUUIDString = getUUID(prefs); mUUIDString = getUUID(prefs);
if (!prefs.contains(PREF_USABILITY_STUDY_MODE)) {
Editor e = prefs.edit();
e.putBoolean(PREF_USABILITY_STUDY_MODE, DEFAULT_USABILITY_STUDY_MODE);
e.apply();
}
sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false); sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false);
prefs.registerOnSharedPreferenceChangeListener(this); prefs.registerOnSharedPreferenceChangeListener(this);
@ -146,6 +153,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
} }
} }
mKeyboardSwitcher = keyboardSwitcher; mKeyboardSwitcher = keyboardSwitcher;
mContext = ims;
mPrefs = prefs;
// TODO: force user to decide at splash screen instead of defaulting to on.
setLoggingAllowed(true);
} }
private void cleanupLoggingDir(final File dir, final long time) { private void cleanupLoggingDir(final File dir, final long time) {
@ -166,8 +178,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
return new File(filesDir, sb.toString()); return new File(filesDir, sb.toString());
} }
public void start() { private void start() {
if (!sIsLogging) { updateSuspendedState();
requestIndicatorRedraw();
if (!isAllowedToLog()) {
// Log.w(TAG, "not in usability mode; not logging"); // Log.w(TAG, "not in usability mode; not logging");
return; return;
} }
@ -175,10 +189,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
Log.w(TAG, "IME storage directory does not exist. Cannot start logging."); Log.w(TAG, "IME storage directory does not exist. Cannot start logging.");
return; return;
} }
if (mMainResearchLog == null || !mMainResearchLog.isAlive()) {
mMainResearchLog = new ResearchLog(createLogFile(mFilesDir));
}
try { try {
if (mMainResearchLog == null || !mMainResearchLog.isAlive()) {
mMainResearchLog = new ResearchLog(createLogFile(mFilesDir));
}
mMainResearchLog.start(); mMainResearchLog.start();
if (mIntentionalResearchLog == null || !mIntentionalResearchLog.isAlive()) { if (mIntentionalResearchLog == null || !mIntentionalResearchLog.isAlive()) {
mIntentionalResearchLog = new ResearchLog(createLogFile(mFilesDir)); mIntentionalResearchLog = new ResearchLog(createLogFile(mFilesDir));
@ -189,15 +203,26 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
} }
} }
public void stop() { /* package */ void stop() {
if (mMainResearchLog != null) { if (mMainResearchLog != null) {
mMainResearchLog.stop(); mMainResearchLog.stop();
} }
if (mIntentionalResearchLog != null) {
mIntentionalResearchLog.stop();
}
}
private void setLoggingAllowed(boolean enableLogging) {
if (mPrefs == null) {
return;
}
Editor e = mPrefs.edit();
e.putBoolean(PREF_USABILITY_STUDY_MODE, enableLogging);
e.apply();
sIsLogging = enableLogging;
} }
public boolean abort() { public boolean abort() {
mIsLoggingSuspended = true;
requestIndicatorRedraw();
boolean didAbortMainLog = false; boolean didAbortMainLog = false;
if (mMainResearchLog != null) { if (mMainResearchLog != null) {
mMainResearchLog.abort(); mMainResearchLog.abort();
@ -209,6 +234,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (mMainResearchLog.isAbortSuccessful()) { if (mMainResearchLog.isAbortSuccessful()) {
didAbortMainLog = true; didAbortMainLog = true;
} }
mMainResearchLog = null;
} }
boolean didAbortIntentionalLog = false; boolean didAbortIntentionalLog = false;
if (mIntentionalResearchLog != null) { if (mIntentionalResearchLog != null) {
@ -221,6 +247,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (mIntentionalResearchLog.isAbortSuccessful()) { if (mIntentionalResearchLog.isAbortSuccessful()) {
didAbortIntentionalLog = true; didAbortIntentionalLog = true;
} }
mIntentionalResearchLog = null;
} }
return didAbortMainLog && didAbortIntentionalLog; return didAbortMainLog && didAbortIntentionalLog;
} }
@ -247,6 +274,34 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
} }
} }
private void restart() {
stop();
start();
}
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) {
mIsLoggingSuspended = false;
}
}
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if (key == null || prefs == null) { if (key == null || prefs == null) {
@ -256,13 +311,15 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (sIsLogging == false) { if (sIsLogging == false) {
abort(); abort();
} }
requestIndicatorRedraw();
} }
/* package */ void presentResearchDialog(final LatinIME latinIME) { /* package */ void presentResearchDialog(final LatinIME latinIME) {
final CharSequence title = latinIME.getString(R.string.english_ime_research_log); final CharSequence title = latinIME.getString(R.string.english_ime_research_log);
final boolean showEnable = mIsLoggingSuspended || !sIsLogging;
final CharSequence[] items = new CharSequence[] { final CharSequence[] items = new CharSequence[] {
latinIME.getString(R.string.note_timestamp_for_researchlog), latinIME.getString(R.string.note_timestamp_for_researchlog),
mIsLoggingSuspended ? latinIME.getString(R.string.enable_session_logging) : showEnable ? latinIME.getString(R.string.enable_session_logging) :
latinIME.getString(R.string.do_not_log_this_session), latinIME.getString(R.string.do_not_log_this_session),
latinIME.getString(R.string.log_whole_session_history), latinIME.getString(R.string.log_whole_session_history),
}; };
@ -277,25 +334,25 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
break; break;
case 1: case 1:
if (mIsLoggingSuspended) { if (showEnable) {
mIsLoggingSuspended = false; if (!sIsLogging) {
requestIndicatorRedraw(); setLoggingAllowed(true);
Toast toast = Toast.makeText(latinIME, }
R.string.notify_session_logging_enabled, Toast.LENGTH_LONG); resumeLogging();
Toast.makeText(latinIME, R.string.notify_session_logging_enabled,
Toast.LENGTH_LONG).show();
} else { } else {
Toast toast = Toast.makeText(latinIME, Toast toast = Toast.makeText(latinIME,
R.string.notify_session_log_deleting, Toast.LENGTH_LONG); R.string.notify_session_log_deleting, Toast.LENGTH_LONG);
toast.show(); toast.show();
boolean isLogDeleted = abort(); boolean isLogDeleted = abort();
final long currentTime = System.currentTimeMillis();
final long resumeTime = currentTime + 1000 * 60 *
SUSPEND_DURATION_IN_MINUTES;
suspendLoggingUntil(resumeTime);
toast.cancel(); toast.cancel();
if (isLogDeleted) { Toast.makeText(latinIME, R.string.notify_logging_suspended,
Toast.makeText(latinIME, R.string.notify_session_log_deleted, Toast.LENGTH_LONG).show();
Toast.LENGTH_LONG).show();
} else {
Toast.makeText(latinIME,
R.string.notify_session_log_not_deleted, Toast.LENGTH_LONG)
.show();
}
} }
break; break;
case 2: case 2:
@ -328,13 +385,15 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
} }
private boolean isAllowedToLog() { private boolean isAllowedToLog() {
return !mIsPasswordView && !mIsLoggingSuspended; return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging;
} }
public void requestIndicatorRedraw() { public void requestIndicatorRedraw() {
// invalidate any existing graphics // invalidate any existing graphics
if (mKeyboardSwitcher != null) { if (IS_SHOWING_INDICATOR) {
mKeyboardSwitcher.getKeyboardView().invalidateAllKeys(); if (mKeyboardSwitcher != null) {
mKeyboardSwitcher.getKeyboardView().invalidateAllKeys();
}
} }
} }
@ -467,6 +526,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
} }
private void publishLogUnit(LogUnit logUnit, boolean isPrivacySensitive) { private void publishLogUnit(LogUnit logUnit, boolean isPrivacySensitive) {
if (!isAllowedToLog()) {
return;
}
if (mMainResearchLog == null) {
return;
}
if (isPrivacySensitive) { if (isPrivacySensitive) {
mMainResearchLog.publishPublicEvents(logUnit); mMainResearchLog.publishPublicEvents(logUnit);
} else { } else {
@ -536,6 +601,18 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
} }
} }
private static String getUUID(final SharedPreferences prefs) {
String uuidString = prefs.getString(PREF_RESEARCH_LOGGER_UUID_STRING, null);
if (null == uuidString) {
UUID uuid = UUID.randomUUID();
uuidString = uuid.toString();
Editor editor = prefs.edit();
editor.putString(PREF_RESEARCH_LOGGER_UUID_STRING, uuidString);
editor.apply();
}
return uuidString;
}
private String scrubWord(String word) { private String scrubWord(String word) {
if (mDictionary == null) { if (mDictionary == null) {
return WORD_REPLACEMENT_STRING; return WORD_REPLACEMENT_STRING;
@ -546,9 +623,62 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
return WORD_REPLACEMENT_STRING; return WORD_REPLACEMENT_STRING;
} }
// Special methods related to startup, shutdown, logging itself
private static final String[] EVENTKEYS_INTENTIONAL_LOG = { private static final String[] EVENTKEYS_INTENTIONAL_LOG = {
"IntentionalLog" "IntentionalLog"
}; };
private static final String[] EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL = {
"LatinIMEOnStartInputViewInternal", "uuid", "packageName", "inputType", "imeOptions",
"fieldId", "display", "model", "prefs", "versionCode", "versionName", "outputFormatVersion"
};
public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
final SharedPreferences prefs) {
final ResearchLogger researchLogger = getInstance();
researchLogger.start();
if (editorInfo != null) {
final Context context = researchLogger.mContext;
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.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);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
}
}
public void latinIME_onFinishInputInternal() {
stop();
}
private static final String[] EVENTKEYS_LATINIME_COMMITTEXT = {
"LatinIMECommitText", "typedWord"
};
public static void latinIME_commitText(final CharSequence typedWord) {
final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
final Object[] values = {
scrubbedWord
};
final ResearchLogger researchLogger = getInstance();
researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_COMMITTEXT, values);
researchLogger.onWordComplete(scrubbedWord);
}
// Regular logging methods
private static final String[] EVENTKEYS_LATINKEYBOARDVIEW_PROCESSMOTIONEVENT = { private static final String[] EVENTKEYS_LATINKEYBOARDVIEW_PROCESSMOTIONEVENT = {
"LatinKeyboardViewProcessMotionEvent", "action", "eventTime", "id", "x", "y", "size", "LatinKeyboardViewProcessMotionEvent", "action", "eventTime", "id", "x", "y", "size",
"pressure" "pressure"
@ -611,19 +741,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION, values); EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION, values);
} }
private static final String[] EVENTKEYS_LATINIME_COMMITTEXT = {
"LatinIMECommitText", "typedWord"
};
public static void latinIME_commitText(final CharSequence typedWord) {
final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
final Object[] values = {
scrubbedWord
};
final ResearchLogger researchLogger = getInstance();
researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_COMMITTEXT, values);
researchLogger.onWordComplete(scrubbedWord);
}
private static final String[] EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT = { private static final String[] EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT = {
"LatinIMEDeleteSurroundingText", "length" "LatinIMEDeleteSurroundingText", "length"
}; };
@ -702,51 +819,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
// Play it safe. Remove privacy-sensitive events. // Play it safe. Remove privacy-sensitive events.
researchLogger.publishLogUnit(researchLogger.mCurrentLogUnit, true); researchLogger.publishLogUnit(researchLogger.mCurrentLogUnit, true);
researchLogger.mCurrentLogUnit = new LogUnit(); researchLogger.mCurrentLogUnit = new LogUnit();
getInstance().restart();
} }
} }
private static final String[] EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL = {
"LatinIMEOnStartInputViewInternal", "uuid", "packageName", "inputType", "imeOptions",
"fieldId", "display", "model", "prefs", "versionCode", "versionName", "outputFormatVersion"
};
public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
final SharedPreferences prefs) {
final ResearchLogger researchLogger = getInstance();
researchLogger.start();
if (editorInfo != null) {
final Context context = researchLogger.mContext;
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.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);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
}
}
private static String getUUID(final SharedPreferences prefs) {
String uuidString = prefs.getString(PREF_RESEARCH_LOGGER_UUID_STRING, null);
if (null == uuidString) {
UUID uuid = UUID.randomUUID();
uuidString = uuid.toString();
Editor editor = prefs.edit();
editor.putString(PREF_RESEARCH_LOGGER_UUID_STRING, uuidString);
editor.apply();
}
return uuidString;
}
private static final String[] EVENTKEYS_LATINIME_ONUPDATESELECTION = { private static final String[] EVENTKEYS_LATINIME_ONUPDATESELECTION = {
"LatinIMEOnUpdateSelection", "lastSelectionStart", "lastSelectionEnd", "oldSelStart", "LatinIMEOnUpdateSelection", "lastSelectionStart", "lastSelectionEnd", "oldSelStart",
"oldSelEnd", "newSelStart", "newSelEnd", "composingSpanStart", "composingSpanEnd", "oldSelEnd", "newSelStart", "newSelEnd", "composingSpanStart", "composingSpanEnd",
@ -873,6 +949,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (keyboard != null) { if (keyboard != null) {
final KeyboardId kid = keyboard.mId; final KeyboardId kid = keyboard.mId;
final boolean isPasswordView = kid.passwordInput(); final boolean isPasswordView = kid.passwordInput();
getInstance().setIsPasswordView(isPasswordView);
final Object[] values = { final Object[] values = {
KeyboardId.elementIdToName(kid.mElementId), KeyboardId.elementIdToName(kid.mElementId),
kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET), kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),

View File

@ -208,7 +208,8 @@ public class Settings extends InputMethodSettingsFragment
if (ProductionFlag.IS_EXPERIMENTAL) { if (ProductionFlag.IS_EXPERIMENTAL) {
if (usabilityStudyPref instanceof CheckBoxPreference) { if (usabilityStudyPref instanceof CheckBoxPreference) {
CheckBoxPreference checkbox = (CheckBoxPreference)usabilityStudyPref; CheckBoxPreference checkbox = (CheckBoxPreference)usabilityStudyPref;
checkbox.setChecked(prefs.getBoolean(PREF_USABILITY_STUDY_MODE, true)); checkbox.setChecked(prefs.getBoolean(PREF_USABILITY_STUDY_MODE,
ResearchLogger.DEFAULT_USABILITY_STUDY_MODE));
checkbox.setSummary(R.string.settings_warning_researcher_mode); checkbox.setSummary(R.string.settings_warning_researcher_mode);
} }
} }