Skip to content
Snippets Groups Projects
Commit 0d54692c authored by Kurt Partridge's avatar Kurt Partridge Committed by Android (Google) Code Review
Browse files

Merge "intentional logging"

parents 91f7086b 223d671f
No related branches found
No related tags found
No related merge requests found
......@@ -42,6 +42,7 @@
-keep class com.android.inputmethod.latin.ResearchLogger {
void flush();
void publishCurrentLogUnit(...);
}
-keep class com.android.inputmethod.keyboard.KeyboardLayoutSet$Builder {
......
......@@ -224,12 +224,22 @@
<!-- 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>
<!-- Title for dialog option to let users reenable logging [CHAR LIMIT=35] -->
<string name="enable_session_logging">Enable session logging</string>
<!-- 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>
<!-- 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>
<!-- 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>
<!-- 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>
<!-- Toast notification that the system has recorded the whole session history [CHAR LIMIT=35] -->
<string name="notify_session_history_logged">Session history logged</string>
<!-- Toast notification that the system has failed to record the whole session history [CHAR LIMIT=35] -->
<string name="notify_session_history_not_logged">Error: Session history NOT logged</string>
<!-- Toast notification that the system is enabling logging [CHAR LIMIT=35] -->
<string name="notify_session_logging_enabled">Session logging enabled</string>
<!-- Preference for input language selection -->
<string name="select_language">Input languages</string>
......
......@@ -353,7 +353,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mPrefs = prefs;
LatinImeLogger.init(this, prefs);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.getInstance().init(this, prefs);
ResearchLogger.getInstance().init(this, prefs, mKeyboardSwitcher);
}
InputMethodManagerCompatWrapper.init(this);
SubtypeSwitcher.init(this);
......
/*
* 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.latin;
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.ResearchLogger.LogUnit;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.BufferedWriter;
import java.io.File;
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.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Logs the use of the LatinIME keyboard.
*
* This class logs operations on the IME keyboard, including what the user has typed.
* Data is stored locally in a file in app-specific storage.
*
* This functionality is off by default. See {@link ProductionFlag#IS_EXPERIMENTAL}.
*/
public class ResearchLog {
private static final String TAG = ResearchLog.class.getSimpleName();
private static final JsonWriter NULL_JSON_WRITER = new JsonWriter(
new OutputStreamWriter(new NullOutputStream()));
final ScheduledExecutorService mExecutor;
/* package */ final File mFile;
private JsonWriter mJsonWriter = NULL_JSON_WRITER; // should never be null
private int mLoggingState;
private static final int LOGGING_STATE_UNSTARTED = 0;
private static final int LOGGING_STATE_RUNNING = 1;
private static final int LOGGING_STATE_STOPPING = 2;
private static final int LOGGING_STATE_STOPPED = 3;
private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
private static class NullOutputStream extends OutputStream {
/** {@inheritDoc} */
@Override
public void write(byte[] buffer, int offset, int count) {
// nop
}
/** {@inheritDoc} */
@Override
public void write(byte[] buffer) {
// nop
}
@Override
public void write(int oneByte) {
}
}
public ResearchLog(File outputFile) {
mExecutor = Executors.newSingleThreadScheduledExecutor();
if (outputFile == null) {
throw new IllegalArgumentException();
}
mFile = outputFile;
mLoggingState = LOGGING_STATE_UNSTARTED;
}
public synchronized void start() throws IOException {
switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED:
mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile)));
mJsonWriter.setLenient(true);
mJsonWriter.beginArray();
mLoggingState = LOGGING_STATE_RUNNING;
break;
case LOGGING_STATE_RUNNING:
case LOGGING_STATE_STOPPING:
case LOGGING_STATE_STOPPED:
break;
}
}
public synchronized void stop() {
switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED:
mLoggingState = LOGGING_STATE_STOPPED;
break;
case LOGGING_STATE_RUNNING:
mExecutor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
try {
mJsonWriter.endArray();
mJsonWriter.flush();
mJsonWriter.close();
} finally {
// the contentprovider only exports data if the writable
// bit is cleared.
boolean success = mFile.setWritable(false, false);
mLoggingState = LOGGING_STATE_STOPPED;
}
return null;
}
});
mExecutor.shutdown();
mLoggingState = LOGGING_STATE_STOPPING;
break;
case LOGGING_STATE_STOPPING:
case LOGGING_STATE_STOPPED:
}
}
public boolean isAlive() {
switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED:
case LOGGING_STATE_RUNNING:
return true;
}
return false;
}
public void waitUntilStopped(int timeoutInMs) throws InterruptedException {
mExecutor.awaitTermination(timeoutInMs, TimeUnit.MILLISECONDS);
}
private boolean isAbortSuccessful;
public boolean isAbortSuccessful() {
return isAbortSuccessful;
}
public synchronized void abort() {
switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED:
mLoggingState = LOGGING_STATE_STOPPED;
isAbortSuccessful = true;
break;
case LOGGING_STATE_RUNNING:
mExecutor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
try {
mJsonWriter.endArray();
mJsonWriter.close();
} finally {
isAbortSuccessful = mFile.delete();
}
return null;
}
});
mExecutor.shutdown();
mLoggingState = LOGGING_STATE_STOPPING;
break;
case LOGGING_STATE_STOPPING:
case LOGGING_STATE_STOPPED:
}
}
/* package */ synchronized void flush() {
switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED:
break;
case LOGGING_STATE_RUNNING:
removeAnyScheduledFlush();
mExecutor.submit(mFlushCallable);
break;
case LOGGING_STATE_STOPPING:
case LOGGING_STATE_STOPPED:
}
}
private Callable<Object> mFlushCallable = new Callable<Object>() {
@Override
public Object call() throws Exception {
mJsonWriter.flush();
return null;
}
};
private ScheduledFuture<Object> mFlushFuture;
private void removeAnyScheduledFlush() {
if (mFlushFuture != null) {
mFlushFuture.cancel(false);
mFlushFuture = null;
}
}
private void scheduleFlush() {
removeAnyScheduledFlush();
mFlushFuture = mExecutor.schedule(mFlushCallable, FLUSH_DELAY_IN_MS, TimeUnit.MILLISECONDS);
}
public synchronized void publishPublicEvents(final LogUnit logUnit) {
switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED:
break;
case LOGGING_STATE_RUNNING:
mExecutor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
logUnit.publishPublicEventsTo(ResearchLog.this);
scheduleFlush();
return null;
}
});
break;
case LOGGING_STATE_STOPPING:
case LOGGING_STATE_STOPPED:
}
}
public synchronized void publishAllEvents(final LogUnit logUnit) {
switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED:
break;
case LOGGING_STATE_RUNNING:
mExecutor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
logUnit.publishAllEventsTo(ResearchLog.this);
scheduleFlush();
return null;
}
});
break;
case LOGGING_STATE_STOPPING:
case LOGGING_STATE_STOPPED:
}
}
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.
try {
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 String) {
mJsonWriter.value((String) value);
} 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.mAltCode);
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");
try {
mJsonWriter.close();
} catch (IllegalStateException e1) {
// assume that this is just the json not being terminated properly.
// ignore
} catch (IOException e1) {
e1.printStackTrace();
} finally {
mJsonWriter = NULL_JSON_WRITER;
}
}
}
}
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment