aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java9
-rw-r--r--java/src/com/android/inputmethod/keyboard/MainKeyboardView.java25
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java20
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java16
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java30
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java32
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java7
-rw-r--r--java/src/com/android/inputmethod/latin/define/ProductionFlag.java7
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java76
-rw-r--r--java/src/com/android/inputmethod/latin/settings/DebugSettings.java1
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsFragment.java5
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java5
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FragmentUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java7
-rw-r--r--java/src/com/android/inputmethod/research/BootBroadcastReceiver.java34
-rw-r--r--java/src/com/android/inputmethod/research/FeedbackActivity.java38
-rw-r--r--java/src/com/android/inputmethod/research/FeedbackFragment.java131
-rw-r--r--java/src/com/android/inputmethod/research/FeedbackLayout.java62
-rw-r--r--java/src/com/android/inputmethod/research/FeedbackLog.java32
-rw-r--r--java/src/com/android/inputmethod/research/FixedLogBuffer.java174
-rw-r--r--java/src/com/android/inputmethod/research/JsonUtils.java162
-rw-r--r--java/src/com/android/inputmethod/research/LogBuffer.java73
-rw-r--r--java/src/com/android/inputmethod/research/LogStatement.java225
-rw-r--r--java/src/com/android/inputmethod/research/LogUnit.java496
-rw-r--r--java/src/com/android/inputmethod/research/LoggingUtils.java38
-rw-r--r--java/src/com/android/inputmethod/research/MainLogBuffer.java287
-rw-r--r--java/src/com/android/inputmethod/research/MotionEventReader.java324
-rw-r--r--java/src/com/android/inputmethod/research/Replayer.java150
-rw-r--r--java/src/com/android/inputmethod/research/ReplayerService.java65
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLog.java298
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogDirectory.java113
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogger.java1885
-rw-r--r--java/src/com/android/inputmethod/research/ResearchSettings.java72
-rw-r--r--java/src/com/android/inputmethod/research/Statistics.java279
-rw-r--r--java/src/com/android/inputmethod/research/Uploader.java171
-rw-r--r--java/src/com/android/inputmethod/research/UploaderService.java88
-rw-r--r--java/src/com/android/inputmethod/research/ui/SplashScreen.java111
37 files changed, 15 insertions, 5535 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index f90ad7975..ca7da6eb8 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -37,9 +37,7 @@ import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.utils.TypefaceUtils;
-import com.android.inputmethod.research.ResearchLogger;
import java.util.HashSet;
@@ -317,13 +315,6 @@ public class KeyboardView extends View {
}
}
- // Research Logging (Development Only Diagnostics) indicator.
- // TODO: Reimplement using a keyboard background image specific to the ResearchLogger,
- // and remove this call.
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().paintIndicator(this, paint, canvas, width, height);
- }
-
mInvalidatedKeys.clear();
mInvalidateAllKeys = false;
}
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index f291a7ed0..4362a242f 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -55,13 +55,11 @@ import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.DebugSettings;
import com.android.inputmethod.latin.utils.CoordinateUtils;
import com.android.inputmethod.latin.utils.SpacebarLanguageUtils;
import com.android.inputmethod.latin.utils.TypefaceUtils;
import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils;
-import com.android.inputmethod.research.ResearchLogger;
import java.util.WeakHashMap;
@@ -387,10 +385,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mSpaceKey = keyboard.getKey(Constants.CODE_SPACE);
final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
mLanguageOnSpacebarTextSize = keyHeight * mLanguageOnSpacebarTextRatio;
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final int orientation = getContext().getResources().getConfiguration().orientation;
- ResearchLogger.mainKeyboardView_setKeyboard(keyboard, orientation);
- }
mAccessibilityDelegate.setKeyboard(keyboard);
}
@@ -552,24 +546,12 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
protected void onAttachedToWindow() {
super.onAttachedToWindow();
installPreviewPlacerView();
- // Notify the ResearchLogger (development only diagnostics) that the keyboard view has
- // been attached. This is needed to properly show the splash screen, which requires that
- // the window token of the KeyboardView be non-null.
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this);
- }
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mDrawingPreviewPlacerView.removeAllViews();
- // Notify the ResearchLogger (development only diagnostics) that the keyboard view has
- // been detached. This is needed to invalidate the reference of {@link MainKeyboardView}
- // to null.
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow();
- }
}
private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) {
@@ -605,9 +587,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
if (key == null) {
return;
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.mainKeyboardView_onLongPress();
- }
final KeyboardActionListener listener = mKeyboardActionListener;
if (key.hasNoPanelAutoMoreKey()) {
final int moreKeyCode = key.getMoreKeys()[0].mCode;
@@ -723,10 +702,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
if (LatinImeLogger.sUsabilityStudy) {
UsabilityStudyLogUtils.writeMotionEvent(me);
}
- // Currently the same "move" event is being logged twice.
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.mainKeyboardView_processMotionEvent(me);
- }
final int index = me.getActionIndex();
final int id = me.getPointerId(index);
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 303886cd3..f80761132 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -35,11 +35,9 @@ import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.utils.CoordinateUtils;
import com.android.inputmethod.latin.utils.ResourceUtils;
-import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
@@ -335,10 +333,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
output, ignoreModifierKey ? " ignoreModifier" : "",
altersCode ? " altersCode" : "", key.isEnabled() ? "" : " disabled"));
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.pointerTracker_callListenerOnCodeInput(key, x, y, ignoreModifierKey,
- altersCode, code);
- }
if (ignoreModifierKey) {
return;
}
@@ -373,10 +367,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
withSliding ? " sliding" : "", ignoreModifierKey ? " ignoreModifier" : "",
key.isEnabled() ? "": " disabled"));
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.pointerTracker_callListenerOnRelease(key, primaryCode, withSliding,
- ignoreModifierKey);
- }
if (ignoreModifierKey) {
return;
}
@@ -396,9 +386,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
if (DEBUG_LISTENER) {
Log.d(TAG, String.format("[%d] onCancelInput", mPointerId));
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.pointerTracker_callListenerOnCancelInput();
- }
sListener.onCancelInput();
}
@@ -702,9 +689,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
Log.w(TAG, String.format("[%d] onDownEvent:"
+ " ignore potential noise: time=%d distance=%d",
mPointerId, deltaT, distance));
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.pointerTracker_onDownEvent(deltaT, distance * distance);
- }
cancelTrackingForAction();
return;
}
@@ -876,10 +860,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
lastX, lastY, Constants.printableCode(oldKey.getCode()),
x, y, Constants.printableCode(key.getCode())));
}
- // TODO: This should be moved to outside of this nested if-clause?
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY);
- }
onUpEventInternal(x, y, eventTime);
onDownEventInternal(x, y, eventTime);
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 23f8a249a..096b946d2 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -318,18 +318,18 @@ public final class BinaryDictionary extends Dictionary {
++len;
}
if (len > 0) {
- final int kindAndFlags = mOutputTypes[j];
- if (blockOffensiveWords
- && 0 != (kindAndFlags & SuggestedWordInfo.KIND_FLAG_POSSIBLY_OFFENSIVE)
- && 0 == (kindAndFlags & SuggestedWordInfo.KIND_FLAG_EXACT_MATCH)) {
+ final SuggestedWordInfo suggestedWordInfo =
+ new SuggestedWordInfo(new String(mOutputCodePoints, start, len),
+ mOutputScores[j], mOutputTypes[j], this /* sourceDict */,
+ mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */,
+ mOutputAutoCommitFirstWordConfidence[0]);
+ if (blockOffensiveWords && suggestedWordInfo.isPossiblyOffensive()
+ && !suggestedWordInfo.isExactMatch()) {
// If we block potentially offensive words, and if the word is possibly
// offensive, then we don't output it unless it's also an exact match.
continue;
}
- suggestions.add(new SuggestedWordInfo(new String(mOutputCodePoints, start, len),
- mOutputScores[j], kindAndFlags, this /* sourceDict */,
- mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */,
- mOutputAutoCommitFirstWordConfidence[0]));
+ suggestions.add(suggestedWordInfo);
}
}
return suggestions;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index aeae6aab4..a3254161d 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -28,7 +28,6 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -38,7 +37,6 @@ import android.net.ConnectivityManager;
import android.os.Debug;
import android.os.IBinder;
import android.os.Message;
-import android.preference.PreferenceManager;
import android.text.InputType;
import android.text.TextUtils;
import android.util.Log;
@@ -90,7 +88,6 @@ import com.android.inputmethod.latin.utils.JniUtils;
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import com.android.inputmethod.latin.utils.StatsUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
-import com.android.inputmethod.research.ResearchLogger;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -494,11 +491,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
loadSettings();
resetSuggest();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().init(this, mKeyboardSwitcher);
- ResearchLogger.getInstance().initDictionary(mDictionaryFacilitator);
- }
-
// Register to receive ringer mode change and network state change.
// Also receive installation and removal of a dictionary pack.
final IntentFilter filter = new IntentFilter();
@@ -631,9 +623,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mDictionaryFacilitator.closeDictionaries();
mSettings.onDestroy();
unregisterReceiver(mConnectivityAndRingerModeChangeReceiver);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().onDestroy();
- }
unregisterReceiver(mDictionaryPackInstallReceiver);
unregisterReceiver(mDictionaryDumpBroadcastReceiver);
PersonalizationDictionarySessionRegistrar.close(this);
@@ -757,10 +746,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
Log.i(TAG, "Starting input. Cursor position = "
+ editorInfo.initialSelStart + "," + editorInfo.initialSelEnd);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- ResearchLogger.latinIME_onStartInputViewInternal(editorInfo, prefs);
- }
if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) {
Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions);
Log.w(TAG, "Use " + getPackageName() + "." + NO_MICROPHONE + " instead");
@@ -905,10 +890,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.cancelUpdateSuggestionStrip();
// Should do the following in onFinishInputInternal but until JB MR2 it's not called :(
mInputLogic.finishInput();
- // Notify ResearchLogger
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput);
- }
}
@Override
@@ -922,11 +903,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
+ ", nss=" + newSelStart + ", nse=" + newSelEnd
+ ", cs=" + composingSpanStart + ", ce=" + composingSpanEnd);
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onUpdateSelection(oldSelStart, oldSelEnd,
- oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart,
- composingSpanEnd, mInputLogic.mConnection);
- }
// If the keyboard is not visible, we don't need to do all the housekeeping work, as it
// will be reset when the keyboard shows up anyway.
@@ -1013,9 +989,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
if (applicationSpecifiedCompletions == null) {
setNeutralSuggestionStrip();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onDisplayCompletions(null);
- }
return;
}
@@ -1027,9 +1000,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
false /* isObsoleteSuggestions */, false /* isPrediction */);
// When in fullscreen mode, show completions generated by the application forcibly
setSuggestedWords(suggestedWords, true /* isSuggestionStripVisible */);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
- }
}
private int getAdjustedBackingViewHeight() {
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index e7c163606..46c015116 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -26,14 +26,12 @@ import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
-import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import com.android.inputmethod.latin.utils.CapsModeUtils;
import com.android.inputmethod.latin.utils.DebugLogUtils;
import com.android.inputmethod.latin.utils.SpannableStringUtils;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TextRange;
-import com.android.inputmethod.research.ResearchLogger;
import java.util.Arrays;
import java.util.regex.Pattern;
@@ -174,9 +172,6 @@ public final class RichInputConnection {
}
if (null != mIC && shouldFinishComposition) {
mIC.finishComposingText();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_finishComposingText();
- }
}
return true;
}
@@ -223,9 +218,6 @@ public final class RichInputConnection {
mComposingText.setLength(0);
if (null != mIC) {
mIC.finishComposingText();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_finishComposingText();
- }
}
}
@@ -363,9 +355,6 @@ public final class RichInputConnection {
}
if (null != mIC) {
mIC.deleteSurroundingText(beforeLength, afterLength);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_deleteSurroundingText(beforeLength, afterLength);
- }
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@@ -374,9 +363,6 @@ public final class RichInputConnection {
mIC = mParent.getCurrentInputConnection();
if (null != mIC) {
mIC.performEditorAction(actionId);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_performEditorAction(actionId);
- }
}
}
@@ -429,9 +415,6 @@ public final class RichInputConnection {
}
if (null != mIC) {
mIC.sendKeyEvent(keyEvent);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_sendKeyEvent(keyEvent);
- }
}
}
@@ -469,9 +452,6 @@ public final class RichInputConnection {
// newCursorPosition != 1.
if (null != mIC) {
mIC.setComposingText(text, newCursorPosition);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_setComposingText(text, newCursorPosition);
- }
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@@ -500,9 +480,6 @@ public final class RichInputConnection {
if (!isIcValid) {
return false;
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_setSelection(start, end);
- }
}
return reloadTextCache();
}
@@ -530,9 +507,6 @@ public final class RichInputConnection {
mComposingText.setLength(0);
if (null != mIC) {
mIC.commitCompletion(completionInfo);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_commitCompletion(completionInfo);
- }
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@@ -765,9 +739,6 @@ public final class RichInputConnection {
deleteSurroundingText(2, 0);
final String singleSpace = " ";
commitText(singleSpace, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_revertDoubleSpacePeriod();
- }
return true;
}
@@ -790,9 +761,6 @@ public final class RichInputConnection {
deleteSurroundingText(2, 0);
final String text = " " + textBeforeCursor.subSequence(0, 1);
commitText(text, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_revertSwapPunctuation();
- }
return true;
}
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 09c672718..72461e17a 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -296,16 +296,15 @@ public class SuggestedWords {
}
public boolean isPossiblyOffensive() {
- return (mKindAndFlags & SuggestedWordInfo.KIND_FLAG_POSSIBLY_OFFENSIVE) != 0;
+ return (mKindAndFlags & KIND_FLAG_POSSIBLY_OFFENSIVE) != 0;
}
public boolean isExactMatch() {
- return (mKindAndFlags & SuggestedWordInfo.KIND_FLAG_EXACT_MATCH) != 0;
+ return (mKindAndFlags & KIND_FLAG_EXACT_MATCH) != 0;
}
public boolean isExactMatchWithIntentionalOmission() {
- return (mKindAndFlags
- & SuggestedWordInfo.KIND_FLAG_EXACT_MATCH_WITH_INTENTIONAL_OMISSION) != 0;
+ return (mKindAndFlags & KIND_FLAG_EXACT_MATCH_WITH_INTENTIONAL_OMISSION) != 0;
}
public void setDebugString(final String str) {
diff --git a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
index 761f457ea..972580298 100644
--- a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
+++ b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
@@ -21,13 +21,6 @@ public final class ProductionFlag {
// This class is not publicly instantiable.
}
- public static final boolean USES_DEVELOPMENT_ONLY_DIAGNOSTICS = false;
-
- // When false, USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG suggests that all guarded
- // class-private DEBUG flags should be false, and any privacy controls should be enforced.
- // USES_DEVELOPMENT_ONLY_DIAGNOSTICS must be false for any production build.
- public static final boolean USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG = false;
-
public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = false;
// When true, enable {@link InputMethodService#onUpdateCursor} callback with
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 2b7024ad6..2530f64f9 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -44,7 +44,6 @@ import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
-import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
@@ -54,7 +53,6 @@ import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
import com.android.inputmethod.latin.utils.RecapitalizeStatus;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TextRange;
-import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
import java.util.TreeSet;
@@ -201,19 +199,11 @@ public final class InputLogic {
resetComposingState(true /* alsoResetLastComposedWord */);
}
handler.postUpdateSuggestionStrip();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS
- && ResearchLogger.RESEARCH_KEY_OUTPUT_TEXT.equals(rawText)) {
- ResearchLogger.getInstance().onResearchKeySelected(mLatinIME);
- return;
- }
final String text = performSpecificTldProcessingOnTextInput(rawText);
if (SpaceState.PHANTOM == mSpaceState) {
promotePhantomSpace(settingsValues);
}
mConnection.commitText(text, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onTextInput(text, false /* isBatchMode */);
- }
mConnection.endBatchEdit();
// Space state must be updated before calling updateShiftState
mSpaceState = SpaceState.NONE;
@@ -244,10 +234,6 @@ public final class InputLogic {
LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
// Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
final Event event = Event.createPunctuationSuggestionPickedEvent(suggestionInfo);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
- false /* isBatchMode */, suggestedWords.mIsPrediction);
- }
return onCodeInput(settingsValues, event, keyboardShiftState, handler);
}
@@ -286,11 +272,6 @@ public final class InputLogic {
LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords);
commitChosenWord(settingsValues, suggestion,
LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion,
- mWordComposer.isBatchMode(), suggestionInfo.mScore,
- suggestionInfo.mKindAndFlags, suggestionInfo.mSourceDict.mDictType);
- }
mConnection.endBatchEdit();
// Don't allow cancellation of manual pick
mLastComposedWord.deactivate();
@@ -403,9 +384,6 @@ public final class InputLogic {
final InputTransaction inputTransaction = new InputTransaction(settingsValues, event,
SystemClock.uptimeMillis(), mSpaceState,
getActualCapsMode(settingsValues, keyboardShiftMode));
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onCodeInput(code, event.mX, event.mY);
- }
if (event.mKeyCode != Constants.CODE_DELETE
|| inputTransaction.mTimestamp > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) {
mDeleteCount = 0;
@@ -854,9 +832,6 @@ public final class InputLogic {
if (needsPrecedingSpace) {
promotePhantomSpace(settingsValues);
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleSeparator(codePoint, mWordComposer.isComposingWord());
- }
if (!shouldAvoidSendingCode) {
sendKeyCodePoint(settingsValues, codePoint);
@@ -932,10 +907,6 @@ public final class InputLogic {
}
if (mWordComposer.isComposingWord()) {
if (mWordComposer.isBatchMode()) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final String word = mWordComposer.getTypedWord();
- ResearchLogger.latinIME_handleBackspace_batch(word, 1);
- }
final String rejectedSuggestion = mWordComposer.getTypedWord();
mWordComposer.reset();
mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
@@ -961,9 +932,6 @@ public final class InputLogic {
// This is triggered on backspace after a key that inputs multiple characters,
// like the smiley key or the .com key.
mConnection.deleteSurroundingText(mEnteredText.length(), 0);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace_cancelTextInput(mEnteredText);
- }
mEnteredText = null;
// If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
// In addition we know that spaceState is false, and that we should not be
@@ -993,10 +961,6 @@ public final class InputLogic {
mConnection.setSelection(mConnection.getExpectedSelectionEnd(),
mConnection.getExpectedSelectionEnd());
mConnection.deleteSurroundingText(numCharsDeleted, 0);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace(numCharsDeleted,
- false /* shouldUncommitLogUnit */);
- }
} else {
// There is no selection, just delete one character.
if (Constants.NOT_A_CURSOR_POSITION == mConnection.getExpectedSelectionEnd()) {
@@ -1031,10 +995,6 @@ public final class InputLogic {
final int lengthToDelete =
Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDelete, 0);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace(lengthToDelete,
- true /* shouldUncommitLogUnit */);
- }
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
final int codePointBeforeCursorToDeleteAgain =
mConnection.getCodePointBeforeCursor();
@@ -1042,10 +1002,6 @@ public final class InputLogic {
final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
codePointBeforeCursorToDeleteAgain) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain,
- true /* shouldUncommitLogUnit */);
- }
}
}
}
@@ -1083,9 +1039,6 @@ public final class InputLogic {
mConnection.deleteSurroundingText(2, 0);
final String text = lastTwo.charAt(1) + " ";
mConnection.commitText(text, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_swapSwapperAndSpace(lastTwo, text);
- }
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
}
}
@@ -1169,10 +1122,6 @@ public final class InputLogic {
final String textToInsert = inputTransaction.mSettingsValues.mSpacingAndPunctuations
.mSentenceSeparatorAndSpace;
mConnection.commitText(textToInsert, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_maybeDoubleSpacePeriod(textToInsert,
- false /* isBatchMode */);
- }
mWordComposer.discardPreviousWordForSuggestion();
return true;
}
@@ -1519,13 +1468,7 @@ public final class InputLogic {
LatinImeLoggerUtils.onSeparator(mLastComposedWord.mSeparatorString,
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_revertCommit(committedWord.toString(),
- originallyTypedWord.toString(),
- mWordComposer.isBatchMode(), mLastComposedWord.mSeparatorString);
- }
- // Don't restart suggestion yet. We'll restart if the user deletes the
- // separator.
+ // Don't restart suggestion yet. We'll restart if the user deletes the separator.
mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
// We have a separator between the word and the cursor: we should show predictions.
inputTransaction.setRequiresUpdateSuggestions();
@@ -1789,9 +1732,6 @@ public final class InputLogic {
*/
// TODO: replace these two parameters with an InputTransaction
private void sendKeyCodePoint(final SettingsValues settingsValues, final int codePoint) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_sendKeyCodePoint(codePoint);
- }
// TODO: Remove this special handling of digit letters.
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
if (codePoint >= '0' && codePoint <= '9') {
@@ -1823,9 +1763,6 @@ public final class InputLogic {
if (settingsValues.shouldInsertSpacesAutomatically()
&& settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
&& !mConnection.textBeforeCursorLooksLikeURL()) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_promotePhantomSpace();
- }
sendKeyCodePoint(settingsValues, Constants.CODE_SPACE);
}
}
@@ -1867,9 +1804,6 @@ public final class InputLogic {
mConnection.setComposingText(batchInputText, 1);
}
mConnection.endBatchEdit();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onEndBatchInput(batchInputText, 0, suggestedWords);
- }
// Space state must be updated before calling updateShiftState
mSpaceState = SpaceState.PHANTOM;
keyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(settingsValues),
@@ -1896,9 +1830,6 @@ public final class InputLogic {
if (!mWordComposer.isComposingWord()) return;
final String typedWord = mWordComposer.getTypedWord();
if (typedWord.length() > 0) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().onWordFinished(typedWord, mWordComposer.isBatchMode());
- }
commitChosenWord(settingsValues, typedWord,
LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, separatorString);
}
@@ -1942,11 +1873,6 @@ public final class InputLogic {
LatinImeLoggerUtils.onAutoCorrection(
typedWord, autoCorrection, separator, mWordComposer);
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final SuggestedWords suggestedWords = mSuggestedWords;
- ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection,
- separator, mWordComposer.isBatchMode(), suggestedWords);
- }
commitChosenWord(settingsValues, autoCorrection,
LastComposedWord.COMMIT_TYPE_DECIDED_WORD, separator);
if (!typedWord.equals(autoCorrection)) {
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index c4c1234fc..5fa15f418 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -76,7 +76,6 @@ public final class DebugSettings extends PreferenceFragment
final CheckBoxPreference checkbox = (CheckBoxPreference)usabilityStudyPref;
checkbox.setChecked(prefs.getBoolean(PREF_USABILITY_STUDY_MODE,
LatinImeLogger.getUsabilityStudyMode(prefs)));
- checkbox.setSummary(R.string.settings_warning_researcher_mode);
}
final Preference statisticsLoggingPref = findPreference(PREF_STATISTICS_LOGGING);
if (statisticsLoggingPref instanceof CheckBoxPreference) {
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index 302ae4390..af46aad96 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -41,7 +41,6 @@ import com.android.inputmethod.keyboard.KeyboardTheme;
import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager;
import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
@@ -152,10 +151,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
miscSettings.removePreference(aboutSettings);
}
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- // The about screen contains items that may be confusing in development-only versions.
- miscSettings.removePreference(aboutSettings);
- }
final boolean showVoiceKeyOption = res.getBoolean(
R.bool.config_enable_show_voice_key_option);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index c400f66c8..4a5a7f004 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -47,11 +47,9 @@ import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.suggestions.MoreSuggestionsView.MoreSuggestionsListener;
import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
-import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
@@ -226,9 +224,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mSuggestedWords = suggestedWords;
mSuggestionsCountInStrip = mLayoutHelper.layoutAndReturnSuggestionCountInStrip(
mSuggestedWords, mSuggestionsStrip, this);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords);
- }
mStripVisibilityGroup.showSuggestionsStrip(isVoiceKeyEnabled());
}
diff --git a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
index 149fbb31a..e300bd1d3 100644
--- a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
@@ -26,7 +26,6 @@ import com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordFragmen
import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
import com.android.inputmethod.latin.userdictionary.UserDictionaryLocalePicker;
import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
-import com.android.inputmethod.research.FeedbackFragment;
import java.util.HashSet;
@@ -43,7 +42,6 @@ public class FragmentUtils {
sLatinImeFragments.add(UserDictionaryList.class.getName());
sLatinImeFragments.add(UserDictionaryLocalePicker.class.getName());
sLatinImeFragments.add(UserDictionarySettings.class.getName());
- sLatinImeFragments.add(FeedbackFragment.class.getName());
}
public static boolean isValidFragment(String fragmentName) {
diff --git a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
index 06826dac0..fac932dba 100644
--- a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
@@ -43,7 +43,6 @@ import java.util.Date;
import java.util.Locale;
public final class UsabilityStudyLogUtils {
- // TODO: remove code duplication with ResearchLog class
private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
private static final String FILENAME = "log.txt";
private final Handler mLoggingHandler;
@@ -190,7 +189,7 @@ public final class UsabilityStudyLogUtils {
return sb.toString();
}
- public void emailResearcherLogsAll() {
+ public void emailUsabilityStudyLogsAll() {
mLoggingHandler.post(new Runnable() {
@Override
public void run() {
@@ -210,7 +209,7 @@ public final class UsabilityStudyLogUtils {
}
mWriter.flush();
final String destPath = Environment.getExternalStorageDirectory()
- + "/research-" + currentDateTimeString + ".log";
+ + "/usability-" + currentDateTimeString + ".log";
final File destFile = new File(destPath);
try {
final FileInputStream srcStream = new FileInputStream(mFile);
@@ -241,7 +240,7 @@ public final class UsabilityStudyLogUtils {
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath));
intent.putExtra(Intent.EXTRA_SUBJECT,
- "[Research Logs] " + currentDateTimeString);
+ "[Usability Study Logs] " + currentDateTimeString);
mIms.startActivity(intent);
}
});
diff --git a/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
deleted file mode 100644
index 4f86526a7..000000000
--- a/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * Arrange for the uploading service to be run on regular intervals.
- */
-public final class BootBroadcastReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
- UploaderService.cancelAndRescheduleUploadingService(context,
- true /* needsRescheduling */);
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/research/FeedbackActivity.java b/java/src/com/android/inputmethod/research/FeedbackActivity.java
deleted file mode 100644
index 520b88d2f..000000000
--- a/java/src/com/android/inputmethod/research/FeedbackActivity.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.app.Activity;
-import android.os.Bundle;
-
-import com.android.inputmethod.latin.R;
-
-public class FeedbackActivity extends Activity {
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.research_feedback_activity);
- final FeedbackLayout layout = (FeedbackLayout) findViewById(R.id.research_feedback_layout);
- layout.setActivity(this);
- }
-
- @Override
- public void onBackPressed() {
- ResearchLogger.getInstance().onLeavingSendFeedbackDialog();
- super.onBackPressed();
- }
-}
diff --git a/java/src/com/android/inputmethod/research/FeedbackFragment.java b/java/src/com/android/inputmethod/research/FeedbackFragment.java
deleted file mode 100644
index 75fbbf1ba..000000000
--- a/java/src/com/android/inputmethod/research/FeedbackFragment.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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.app.Fragment;
-import android.os.Bundle;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.EditText;
-import android.widget.Toast;
-
-import com.android.inputmethod.latin.R;
-
-public class FeedbackFragment extends Fragment implements OnClickListener {
- private static final String TAG = FeedbackFragment.class.getSimpleName();
-
- public static final String KEY_FEEDBACK_STRING = "FeedbackString";
- public static final String KEY_INCLUDE_ACCOUNT_NAME = "IncludeAccountName";
- public static final String KEY_HAS_USER_RECORDING = "HasRecording";
-
- private EditText mEditText;
- private CheckBox mIncludingAccountNameCheckBox;
- private CheckBox mIncludingUserRecordingCheckBox;
- private Button mSendButton;
- private Button mCancelButton;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.research_feedback_fragment_layout, container,
- false);
- mEditText = (EditText) view.findViewById(R.id.research_feedback_contents);
- mEditText.requestFocus();
- mIncludingAccountNameCheckBox = (CheckBox) view.findViewById(
- R.id.research_feedback_include_account_name);
- mIncludingUserRecordingCheckBox = (CheckBox) view.findViewById(
- R.id.research_feedback_include_recording_checkbox);
- mIncludingUserRecordingCheckBox.setOnClickListener(this);
-
- mSendButton = (Button) view.findViewById(R.id.research_feedback_send_button);
- mSendButton.setOnClickListener(this);
- mCancelButton = (Button) view.findViewById(R.id.research_feedback_cancel_button);
- mCancelButton.setOnClickListener(this);
-
- if (savedInstanceState != null) {
- restoreState(savedInstanceState);
- } else {
- final Bundle bundle = getActivity().getIntent().getExtras();
- if (bundle != null) {
- restoreState(bundle);
- }
- }
- return view;
- }
-
- @Override
- public void onClick(final View view) {
- final ResearchLogger researchLogger = ResearchLogger.getInstance();
- if (view == mIncludingUserRecordingCheckBox) {
- if (mIncludingUserRecordingCheckBox.isChecked()) {
- final Bundle bundle = new Bundle();
- onSaveInstanceState(bundle);
-
- // Let the user make a recording
- getActivity().finish();
-
- researchLogger.setFeedbackDialogBundle(bundle);
- researchLogger.onLeavingSendFeedbackDialog();
- researchLogger.startRecording();
- }
- } else if (view == mSendButton) {
- final Editable editable = mEditText.getText();
- final String feedbackContents = editable.toString();
- if (TextUtils.isEmpty(feedbackContents)) {
- Toast.makeText(getActivity(),
- R.string.research_feedback_empty_feedback_error_message,
- Toast.LENGTH_LONG).show();
- } else {
- final boolean isIncludingAccountName = mIncludingAccountNameCheckBox.isChecked();
- researchLogger.sendFeedback(feedbackContents, false /* isIncludingHistory */,
- isIncludingAccountName, mIncludingUserRecordingCheckBox.isChecked());
- getActivity().finish();
- researchLogger.setFeedbackDialogBundle(null);
- researchLogger.onLeavingSendFeedbackDialog();
- }
- } else if (view == mCancelButton) {
- Log.d(TAG, "Finishing");
- getActivity().finish();
- researchLogger.setFeedbackDialogBundle(null);
- researchLogger.onLeavingSendFeedbackDialog();
- } else {
- Log.e(TAG, "Unknown view passed to FeedbackFragment.onClick()");
- }
- }
-
- @Override
- public void onSaveInstanceState(final Bundle bundle) {
- final String savedFeedbackString = mEditText.getText().toString();
-
- bundle.putString(KEY_FEEDBACK_STRING, savedFeedbackString);
- bundle.putBoolean(KEY_INCLUDE_ACCOUNT_NAME, mIncludingAccountNameCheckBox.isChecked());
- bundle.putBoolean(KEY_HAS_USER_RECORDING, mIncludingUserRecordingCheckBox.isChecked());
- }
-
- private void restoreState(final Bundle bundle) {
- mEditText.setText(bundle.getString(KEY_FEEDBACK_STRING));
- mIncludingAccountNameCheckBox.setChecked(bundle.getBoolean(KEY_INCLUDE_ACCOUNT_NAME));
- mIncludingUserRecordingCheckBox.setChecked(bundle.getBoolean(KEY_HAS_USER_RECORDING));
- }
-}
diff --git a/java/src/com/android/inputmethod/research/FeedbackLayout.java b/java/src/com/android/inputmethod/research/FeedbackLayout.java
deleted file mode 100644
index d283d14b2..000000000
--- a/java/src/com/android/inputmethod/research/FeedbackLayout.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.app.Activity;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.widget.LinearLayout;
-
-public class FeedbackLayout extends LinearLayout {
- private Activity mActivity;
-
- public FeedbackLayout(Context context) {
- super(context);
- }
-
- public FeedbackLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public FeedbackLayout(Context context, AttributeSet attrs, int defstyle) {
- super(context, attrs, defstyle);
- }
-
- public void setActivity(Activity activity) {
- mActivity = activity;
- }
-
- @Override
- public boolean dispatchKeyEventPreIme(KeyEvent event) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
- KeyEvent.DispatcherState state = getKeyDispatcherState();
- if (state != null) {
- if (event.getAction() == KeyEvent.ACTION_DOWN
- && event.getRepeatCount() == 0) {
- state.startTracking(event, this);
- return true;
- } else if (event.getAction() == KeyEvent.ACTION_UP
- && !event.isCanceled() && state.isTracking(event)) {
- mActivity.onBackPressed();
- return true;
- }
- }
- }
- return super.dispatchKeyEventPreIme(event);
- }
-}
diff --git a/java/src/com/android/inputmethod/research/FeedbackLog.java b/java/src/com/android/inputmethod/research/FeedbackLog.java
deleted file mode 100644
index 5af194c32..000000000
--- a/java/src/com/android/inputmethod/research/FeedbackLog.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2013 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.Context;
-
-import java.io.File;
-
-public class FeedbackLog extends ResearchLog {
- public FeedbackLog(final File outputFile, final Context context) {
- super(outputFile, context);
- }
-
- @Override
- public boolean isFeedbackLog() {
- return true;
- }
-}
diff --git a/java/src/com/android/inputmethod/research/FixedLogBuffer.java b/java/src/com/android/inputmethod/research/FixedLogBuffer.java
deleted file mode 100644
index 210015643..000000000
--- a/java/src/com/android/inputmethod/research/FixedLogBuffer.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * 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 java.util.ArrayList;
-import java.util.LinkedList;
-
-/**
- * A buffer that holds a fixed number of LogUnits.
- *
- * LogUnits are added in and shifted out in temporal order. Only a subset of the LogUnits are
- * actual words; the other LogUnits do not count toward the word limit. Once the buffer reaches
- * capacity, adding another LogUnit that is a word evicts the oldest LogUnits out one at a time to
- * stay under the capacity limit.
- *
- * This variant of a LogBuffer has a limited memory footprint because of its limited size. This
- * makes it useful, for example, for recording a window of the user's most recent actions in case
- * they want to report an observed error that they do not know how to reproduce.
- */
-public class FixedLogBuffer extends LogBuffer {
- /* package for test */ int mWordCapacity;
- // The number of members of mLogUnits that are actual words.
- private int mNumActualWords;
-
- /**
- * Create a new LogBuffer that can hold a fixed number of LogUnits that are words (and
- * unlimited number of non-word LogUnits), and that outputs its result to a researchLog.
- *
- * @param wordCapacity maximum number of words
- */
- public FixedLogBuffer(final int wordCapacity) {
- super();
- if (wordCapacity <= 0) {
- throw new IllegalArgumentException("wordCapacity must be 1 or greater.");
- }
- mWordCapacity = wordCapacity;
- mNumActualWords = 0;
- }
-
- /**
- * Adds a new LogUnit to the front of the LIFO queue, evicting existing LogUnit's
- * (oldest first) if word capacity is reached.
- */
- @Override
- public void shiftIn(final LogUnit newLogUnit) {
- if (!newLogUnit.hasOneOrMoreWords()) {
- // This LogUnit doesn't contain any word, so it doesn't count toward the word-limit.
- super.shiftIn(newLogUnit);
- return;
- }
- final int numWordsIncoming = newLogUnit.getNumWords();
- if (mNumActualWords >= mWordCapacity) {
- // Give subclass a chance to handle the buffer full condition by shifting out logUnits.
- // TODO: Tell onBufferFull() how much space it needs to make to avoid forced eviction.
- onBufferFull();
- // If still full, evict.
- if (mNumActualWords >= mWordCapacity) {
- shiftOutWords(numWordsIncoming);
- }
- }
- super.shiftIn(newLogUnit);
- mNumActualWords += numWordsIncoming;
- }
-
- @Override
- public LogUnit unshiftIn() {
- final LogUnit logUnit = super.unshiftIn();
- if (logUnit != null && logUnit.hasOneOrMoreWords()) {
- mNumActualWords -= logUnit.getNumWords();
- }
- return logUnit;
- }
-
- public int getNumWords() {
- return mNumActualWords;
- }
-
- /**
- * Removes all LogUnits from the buffer without calling onShiftOut().
- */
- @Override
- public void clear() {
- super.clear();
- mNumActualWords = 0;
- }
-
- /**
- * Called when the buffer has just shifted in one more word than its maximum, and its about to
- * shift out LogUnits to bring it back down to the maximum.
- *
- * Base class does nothing; subclasses may override if they want to record non-privacy sensitive
- * events that fall off the end.
- */
- protected void onBufferFull() {
- }
-
- @Override
- public LogUnit shiftOut() {
- final LogUnit logUnit = super.shiftOut();
- if (logUnit != null && logUnit.hasOneOrMoreWords()) {
- mNumActualWords -= logUnit.getNumWords();
- }
- return logUnit;
- }
-
- /**
- * Remove LogUnits from the front of the LogBuffer until {@code numWords} have been removed.
- *
- * If there are less than {@code numWords} in the buffer, shifts out all {@code LogUnit}s.
- *
- * @param numWords the minimum number of words in {@link LogUnit}s to shift out
- * @return the number of actual words LogUnit}s shifted out
- */
- protected int shiftOutWords(final int numWords) {
- int numWordsShiftedOut = 0;
- do {
- final LogUnit logUnit = shiftOut();
- if (logUnit == null) break;
- numWordsShiftedOut += logUnit.getNumWords();
- } while (numWordsShiftedOut < numWords);
- return numWordsShiftedOut;
- }
-
- public void shiftOutAll() {
- final LinkedList<LogUnit> logUnits = getLogUnits();
- while (!logUnits.isEmpty()) {
- shiftOut();
- }
- mNumActualWords = 0;
- }
-
- /**
- * Returns a list of {@link LogUnit}s at the front of the buffer that have words associated with
- * them.
- *
- * There will be no more than {@code n} words in the returned list. So if 2 words are
- * requested, and the first LogUnit has 3 words, it is not returned. If 2 words are requested,
- * and the first LogUnit has only 1 word, and the next LogUnit 2 words, only the first LogUnit
- * is returned. If the first LogUnit has no words associated with it, and the second LogUnit
- * has three words, then only the first LogUnit (which has no associated words) is returned. If
- * there are not enough LogUnits in the buffer to meet the word requirement, then all LogUnits
- * will be returned.
- *
- * @param n The maximum number of {@link LogUnit}s with words to return.
- * @return The list of the {@link LogUnit}s containing the first n words
- */
- public ArrayList<LogUnit> peekAtFirstNWords(int n) {
- final LinkedList<LogUnit> logUnits = getLogUnits();
- // Allocate space for n*2 logUnits. There will be at least n, one for each word, and
- // there may be additional for punctuation, between-word commands, etc. This should be
- // enough that reallocation won't be necessary.
- final ArrayList<LogUnit> resultList = new ArrayList<>(n * 2);
- for (final LogUnit logUnit : logUnits) {
- n -= logUnit.getNumWords();
- if (n < 0) break;
- resultList.add(logUnit);
- }
- return resultList;
- }
-}
diff --git a/java/src/com/android/inputmethod/research/JsonUtils.java b/java/src/com/android/inputmethod/research/JsonUtils.java
deleted file mode 100644
index 63c08e8bb..000000000
--- a/java/src/com/android/inputmethod/research/JsonUtils.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * 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.MotionEvent;
-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;
-
-/**
- * Routines for mapping classes and variables to JSON representations for logging.
- */
-/* 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.getCode());
- jsonWriter.name("altCode").value(key.getAltCode());
- jsonWriter.name("x").value(key.getX());
- jsonWriter.name("y").value(key.getY());
- jsonWriter.name("w").value(key.getWidth());
- jsonWriter.name("h").value(key.getHeight());
- 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.isPunctuationSuggestions());
- jsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions);
- jsonWriter.name("isPrediction").value(words.mIsPrediction);
- jsonWriter.name("suggestedWords");
- jsonWriter.beginArray();
- final int size = words.size();
- for (int j = 0; j < size; j++) {
- final SuggestedWordInfo wordInfo = words.getInfo(j);
- jsonWriter.beginObject();
- jsonWriter.name("word").value(wordInfo.toString());
- jsonWriter.name("score").value(wordInfo.mScore);
- jsonWriter.name("kind").value(wordInfo.getKind());
- jsonWriter.name("sourceDict").value(wordInfo.mSourceDict.mDictType);
- jsonWriter.endObject();
- }
- jsonWriter.endArray();
- jsonWriter.endObject();
- }
-
- /* package */ static void writeJson(final MotionEvent me, final JsonWriter jsonWriter)
- throws IOException {
- jsonWriter.beginObject();
- jsonWriter.name("pointerIds");
- jsonWriter.beginArray();
- final int pointerCount = me.getPointerCount();
- for (int index = 0; index < pointerCount; index++) {
- jsonWriter.value(me.getPointerId(index));
- }
- jsonWriter.endArray();
-
- jsonWriter.name("xyt");
- jsonWriter.beginArray();
- final int historicalSize = me.getHistorySize();
- for (int index = 0; index < historicalSize; index++) {
- jsonWriter.beginObject();
- jsonWriter.name("t");
- jsonWriter.value(me.getHistoricalEventTime(index));
- jsonWriter.name("d");
- jsonWriter.beginArray();
- for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) {
- jsonWriter.beginObject();
- jsonWriter.name("x");
- jsonWriter.value(me.getHistoricalX(pointerIndex, index));
- jsonWriter.name("y");
- jsonWriter.value(me.getHistoricalY(pointerIndex, index));
- jsonWriter.endObject();
- }
- jsonWriter.endArray();
- jsonWriter.endObject();
- }
- jsonWriter.beginObject();
- jsonWriter.name("t");
- jsonWriter.value(me.getEventTime());
- jsonWriter.name("d");
- jsonWriter.beginArray();
- for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) {
- jsonWriter.beginObject();
- jsonWriter.name("x");
- jsonWriter.value(me.getX(pointerIndex));
- jsonWriter.name("y");
- jsonWriter.value(me.getY(pointerIndex));
- jsonWriter.endObject();
- }
- jsonWriter.endArray();
- jsonWriter.endObject();
- jsonWriter.endArray();
- jsonWriter.endObject();
- }
-}
diff --git a/java/src/com/android/inputmethod/research/LogBuffer.java b/java/src/com/android/inputmethod/research/LogBuffer.java
deleted file mode 100644
index 0d9c357da..000000000
--- a/java/src/com/android/inputmethod/research/LogBuffer.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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 java.util.LinkedList;
-
-/**
- * Maintain a FIFO queue of LogUnits.
- *
- * This class provides an unbounded queue. This is useful when the user is aware that their actions
- * are being recorded, such as when they are trying to reproduce a bug. In this case, there should
- * not be artificial restrictions on how many events that can be saved.
- */
-public class LogBuffer {
- // TODO: Gracefully handle situations in which this LogBuffer is consuming too much memory.
- // This may happen, for example, if the user has forgotten that data is being logged.
- private final LinkedList<LogUnit> mLogUnits;
-
- public LogBuffer() {
- mLogUnits = new LinkedList<>();
- }
-
- protected LinkedList<LogUnit> getLogUnits() {
- return mLogUnits;
- }
-
- public void clear() {
- mLogUnits.clear();
- }
-
- public void shiftIn(final LogUnit logUnit) {
- mLogUnits.add(logUnit);
- }
-
- public LogUnit unshiftIn() {
- if (mLogUnits.isEmpty()) {
- return null;
- }
- return mLogUnits.removeLast();
- }
-
- public LogUnit peekLastLogUnit() {
- if (mLogUnits.isEmpty()) {
- return null;
- }
- return mLogUnits.peekLast();
- }
-
- public boolean isEmpty() {
- return mLogUnits.isEmpty();
- }
-
- public LogUnit shiftOut() {
- if (isEmpty()) {
- return null;
- }
- return mLogUnits.removeFirst();
- }
-}
diff --git a/java/src/com/android/inputmethod/research/LogStatement.java b/java/src/com/android/inputmethod/research/LogStatement.java
deleted file mode 100644
index 06b918af5..000000000
--- a/java/src/com/android/inputmethod/research/LogStatement.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2013 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.util.Log;
-import android.view.MotionEvent;
-import android.view.inputmethod.CompletionInfo;
-
-import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.define.ProductionFlag;
-
-import java.io.IOException;
-
-/**
- * A template for typed information stored in the logs.
- *
- * A LogStatement contains a name, keys, and flags about whether the {@code Object[] values}
- * associated with the {@code String[] keys} are likely to reveal information about the user. The
- * actual values are stored separately.
- */
-public class LogStatement {
- private static final String TAG = LogStatement.class.getSimpleName();
- private static final boolean DEBUG = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
-
- // Constants for particular statements
- public static final String TYPE_POINTER_TRACKER_CALL_LISTENER_ON_CODE_INPUT =
- "PointerTrackerCallListenerOnCodeInput";
- public static final String KEY_CODE = "code";
- public static final String VALUE_RESEARCH = "research";
- public static final String TYPE_MAIN_KEYBOARD_VIEW_ON_LONG_PRESS =
- "MainKeyboardViewOnLongPress";
- public static final String ACTION = "action";
- public static final String VALUE_DOWN = "DOWN";
- public static final String TYPE_MOTION_EVENT = "MotionEvent";
- public static final String KEY_IS_LOGGING_RELATED = "isLoggingRelated";
-
- // Keys for internal key/value pairs
- private static final String CURRENT_TIME_KEY = "_ct";
- private static final String UPTIME_KEY = "_ut";
- private static final String EVENT_TYPE_KEY = "_ty";
-
- // Name specifying the LogStatement type.
- private final String mType;
-
- // 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.
- private 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".
- private final boolean mIsPotentiallyRevealing;
-
- // mKeys stores the names that are the attributes in the output json objects
- private 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) {
- mType = name;
- mIsPotentiallyPrivate = isPotentiallyPrivate;
- mIsPotentiallyRevealing = isPotentiallyRevealing;
- mKeys = (keys == null) ? NULL_KEYS : keys;
- }
-
- public String getType() {
- return mType;
- }
-
- public boolean isPotentiallyPrivate() {
- return mIsPotentiallyPrivate;
- }
-
- public boolean isPotentiallyRevealing() {
- return mIsPotentiallyRevealing;
- }
-
- public String[] getKeys() {
- return mKeys;
- }
-
- /**
- * Utility function to test whether a key-value pair exists in a LogStatement.
- *
- * A LogStatement is really just a template -- it does not contain the values, only the
- * keys. So the values must be passed in as an argument.
- *
- * @param queryKey the String that is tested by {@code String.equals()} to the keys in the
- * LogStatement
- * @param queryValue an Object that must be {@code Object.equals()} to the key's corresponding
- * value in the {@code values} array
- * @param values the values corresponding to mKeys
- *
- * @returns {@true} if {@code queryKey} exists in the keys for this LogStatement, and {@code
- * queryValue} matches the corresponding value in {@code values}
- *
- * @throws IllegalArgumentException if {@code values.length} is not equal to keys().length()
- */
- public boolean containsKeyValuePair(final String queryKey, final Object queryValue,
- final Object[] values) {
- if (mKeys.length != values.length) {
- throw new IllegalArgumentException("Mismatched number of keys and values.");
- }
- final int length = mKeys.length;
- for (int i = 0; i < length; i++) {
- if (mKeys[i].equals(queryKey) && values[i].equals(queryValue)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Utility function to set a value in a LogStatement.
- *
- * A LogStatement is really just a template -- it does not contain the values, only the
- * keys. So the values must be passed in as an argument.
- *
- * @param queryKey the String that is tested by {@code String.equals()} to the keys in the
- * LogStatement
- * @param values the array of values corresponding to mKeys
- * @param newValue the replacement value to go into the {@code values} array
- *
- * @returns {@true} if the key exists and the value was successfully set, {@false} otherwise
- *
- * @throws IllegalArgumentException if {@code values.length} is not equal to keys().length()
- */
- public boolean setValue(final String queryKey, final Object[] values, final Object newValue) {
- if (mKeys.length != values.length) {
- throw new IllegalArgumentException("Mismatched number of keys and values.");
- }
- final int length = mKeys.length;
- for (int i = 0; i < length; i++) {
- if (mKeys[i].equals(queryKey)) {
- values[i] = newValue;
- return true;
- }
- }
- return false;
- }
-
- /**
- * Write the contents out through jsonWriter.
- *
- * The JsonWriter class must have already had {@code JsonWriter.beginArray} called on it.
- *
- * Note that this method is not thread safe for the same jsonWriter. Callers must ensure
- * thread safety.
- */
- public boolean outputToLocked(final JsonWriter jsonWriter, final Long time,
- final Object... values) {
- if (DEBUG) {
- if (mKeys.length != values.length) {
- Log.d(TAG, "Key and Value list sizes do not match. " + mType);
- }
- }
- try {
- jsonWriter.beginObject();
- jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
- jsonWriter.name(UPTIME_KEY).value(time);
- jsonWriter.name(EVENT_TYPE_KEY).value(mType);
- final int length = values.length;
- for (int i = 0; i < length; i++) {
- jsonWriter.name(mKeys[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 instanceof MotionEvent) {
- JsonUtils.writeJson((MotionEvent) value, jsonWriter);
- } else if (value == null) {
- jsonWriter.nullValue();
- } else {
- if (DEBUG) {
- 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;
- }
-}
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
deleted file mode 100644
index 1750751a7..000000000
--- a/java/src/com/android/inputmethod/research/LogUnit.java
+++ /dev/null
@@ -1,496 +0,0 @@
-/*
- * 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.os.SystemClock;
-import android.text.TextUtils;
-import android.util.JsonWriter;
-import android.util.Log;
-
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.define.ProductionFlag;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.regex.Pattern;
-
-/**
- * A group of log statements related to each other.
- *
- * A LogUnit is collection of LogStatements, each of which is generated by at a particular point
- * in the code. (There is no LogStatement class; the data is stored across the instance variables
- * here.) A single LogUnit's statements can correspond to all the calls made while in the same
- * composing region, or all the calls between committing the last composing region, and the first
- * character of the next composing region.
- *
- * Individual statements in a log may be marked as potentially private. If so, then they are only
- * published to a ResearchLog if the ResearchLogger determines that publishing the entire LogUnit
- * will not violate the user's privacy. Checks for this may include whether other LogUnits have
- * been published recently, or whether the LogUnit contains numbers, etc.
- */
-public class LogUnit {
- private static final String TAG = LogUnit.class.getSimpleName();
- private static final boolean DEBUG = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
-
- private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+");
- private static final String[] EMPTY_STRING_ARRAY = new String[0];
-
- 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;
- // Words that this LogUnit generates. Should be null if the data in the LogUnit does not
- // generate a genuine word (i.e. separators alone do not count as a word). Should never be
- // empty. Note that if the user types spaces explicitly, then normally mWords should contain
- // only a single word; it will only contain space-separate multiple words if the user does not
- // enter a space, and the system enters one automatically.
- private String mWords;
- private String[] mWordArray = EMPTY_STRING_ARRAY;
- private boolean mMayContainDigit;
- private boolean mIsPartOfMegaword;
- private boolean mContainsUserDeletions;
-
- // mCorrectionType indicates whether the word was corrected at all, and if so, the nature of the
- // correction.
- private int mCorrectionType;
- // LogUnits start in this state. If a word is entered without being corrected, it will have
- // this CorrectiontType.
- public static final int CORRECTIONTYPE_NO_CORRECTION = 0;
- // The LogUnit was corrected manually by the user in an unspecified way.
- public static final int CORRECTIONTYPE_CORRECTION = 1;
- // The LogUnit was corrected manually by the user to a word not in the list of suggestions of
- // the first word typed here. (Note: this is a heuristic value, it may be incorrect, for
- // example, if the user repositions the cursor).
- public static final int CORRECTIONTYPE_DIFFERENT_WORD = 2;
- // The LogUnit was corrected manually by the user to a word that was in the list of suggestions
- // of the first word typed here. (Again, a heuristic). It is probably a typo correction.
- public static final int CORRECTIONTYPE_TYPO = 3;
- // TODO: Rather than just tracking the current state, keep a historical record of the LogUnit's
- // state and statistics. This should include how many times it has been corrected, whether
- // other LogUnit edits were done between edits to this LogUnit, etc. Also track when a LogUnit
- // previously contained a word, but was corrected to empty (because it was deleted, and there is
- // no known replacement).
-
- private SuggestedWords mSuggestedWords;
-
- public LogUnit() {
- mLogStatementList = new ArrayList<>();
- mValuesList = new ArrayList<>();
- mTimeList = new ArrayList<>();
- mIsPartOfMegaword = false;
- mCorrectionType = CORRECTIONTYPE_NO_CORRECTION;
- mSuggestedWords = null;
- }
-
- 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;
- mCorrectionType = CORRECTIONTYPE_NO_CORRECTION;
- mSuggestedWords = null;
- }
-
- 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);
- mTimeList.add(time);
- }
-
- /**
- * Publish the contents of this LogUnit to {@code researchLog}.
- *
- * For each publishable {@code LogStatement}, invoke {@link LogStatement#outputToLocked}.
- *
- * @param researchLog where to publish the contents of this {@code LogUnit}
- * @param canIncludePrivateData whether the private data in this {@code LogUnit} should be
- * included
- *
- * @throws IOException if publication to the log file is not possible
- */
- public synchronized void publishTo(final ResearchLog researchLog,
- final boolean canIncludePrivateData) throws IOException {
- // Write out any logStatement that passes the privacy filter.
- final int size = mLogStatementList.size();
- if (size != 0) {
- // Note that jsonWriter is only set to a non-null value if the logUnit start text is
- // output and at least one logStatement is output.
- JsonWriter jsonWriter = researchLog.getInitializedJsonWriterLocked();
- outputLogUnitStart(jsonWriter, canIncludePrivateData);
- for (int i = 0; i < size; i++) {
- final LogStatement logStatement = mLogStatementList.get(i);
- if (!canIncludePrivateData && logStatement.isPotentiallyPrivate()) {
- continue;
- }
- if (mIsPartOfMegaword && logStatement.isPotentiallyRevealing()) {
- continue;
- }
- logStatement.outputToLocked(jsonWriter, mTimeList.get(i), mValuesList.get(i));
- }
- outputLogUnitStop(jsonWriter);
- }
- }
-
- private static final String WORD_KEY = "_wo";
- private static final String NUM_WORDS_KEY = "_nw";
- private static final String CORRECTION_TYPE_KEY = "_corType";
- private static final String LOG_UNIT_BEGIN_KEY = "logUnitStart";
- private static final String LOG_UNIT_END_KEY = "logUnitEnd";
-
- final LogStatement LOGSTATEMENT_LOG_UNIT_BEGIN_WITH_PRIVATE_DATA =
- new LogStatement(LOG_UNIT_BEGIN_KEY, false /* isPotentiallyPrivate */,
- false /* isPotentiallyRevealing */, WORD_KEY, CORRECTION_TYPE_KEY,
- NUM_WORDS_KEY);
- final LogStatement LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA =
- new LogStatement(LOG_UNIT_BEGIN_KEY, false /* isPotentiallyPrivate */,
- false /* isPotentiallyRevealing */, NUM_WORDS_KEY);
- private void outputLogUnitStart(final JsonWriter jsonWriter,
- final boolean canIncludePrivateData) {
- final LogStatement logStatement;
- if (canIncludePrivateData) {
- LOGSTATEMENT_LOG_UNIT_BEGIN_WITH_PRIVATE_DATA.outputToLocked(jsonWriter,
- SystemClock.uptimeMillis(), getWordsAsString(), getCorrectionType(),
- getNumWords());
- } else {
- LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA.outputToLocked(jsonWriter,
- SystemClock.uptimeMillis(), getNumWords());
- }
- }
-
- final LogStatement LOGSTATEMENT_LOG_UNIT_END =
- new LogStatement(LOG_UNIT_END_KEY, false /* isPotentiallyPrivate */,
- false /* isPotentiallyRevealing */);
- private void outputLogUnitStop(final JsonWriter jsonWriter) {
- LOGSTATEMENT_LOG_UNIT_END.outputToLocked(jsonWriter, SystemClock.uptimeMillis());
- }
-
- /**
- * Mark the current logUnit as containing data to generate {@code newWords}.
- *
- * If {@code setWord()} was previously called for this LogUnit, then the method will try to
- * determine what kind of correction it is, and update its internal state of the correctionType
- * accordingly.
- *
- * @param newWords The words this LogUnit generates. Caller should not pass null or the empty
- * string.
- */
- public void setWords(final String newWords) {
- if (hasOneOrMoreWords()) {
- // The word was already set once, and it is now being changed. See if the new word
- // is close to the old word. If so, then the change is probably a typo correction.
- // If not, the user may have decided to enter a different word, so flag it.
- if (mSuggestedWords != null) {
- if (isInSuggestedWords(newWords, mSuggestedWords)) {
- mCorrectionType = CORRECTIONTYPE_TYPO;
- } else {
- mCorrectionType = CORRECTIONTYPE_DIFFERENT_WORD;
- }
- } else {
- // No suggested words, so it's not clear whether it's a typo or different word.
- // Mark it as a generic correction.
- mCorrectionType = CORRECTIONTYPE_CORRECTION;
- }
- } else {
- mCorrectionType = CORRECTIONTYPE_NO_CORRECTION;
- }
- mWords = newWords;
-
- // Update mWordArray
- mWordArray = (TextUtils.isEmpty(mWords)) ? EMPTY_STRING_ARRAY
- : WHITESPACE_PATTERN.split(mWords);
- if (mWordArray.length > 0 && TextUtils.isEmpty(mWordArray[0])) {
- // Empty string at beginning of array. Must have been whitespace at the start of the
- // word. Remove the empty string.
- mWordArray = Arrays.copyOfRange(mWordArray, 1, mWordArray.length);
- }
- }
-
- public String getWordsAsString() {
- return mWords;
- }
-
- /**
- * Retuns the words generated by the data in this LogUnit.
- *
- * The first word may be an empty string, if the data in the LogUnit started by generating
- * whitespace.
- *
- * @return the array of words. an empty list of there are no words associated with this LogUnit.
- */
- public String[] getWordsAsStringArray() {
- return mWordArray;
- }
-
- public boolean hasOneOrMoreWords() {
- return mWordArray.length >= 1;
- }
-
- public int getNumWords() {
- return mWordArray.length;
- }
-
- // TODO: Refactor to eliminate getter/setters
- public void setMayContainDigit() {
- mMayContainDigit = true;
- }
-
- // TODO: Refactor to eliminate getter/setters
- public boolean mayContainDigit() {
- return mMayContainDigit;
- }
-
- // TODO: Refactor to eliminate getter/setters
- public void setContainsUserDeletions() {
- mContainsUserDeletions = true;
- }
-
- // TODO: Refactor to eliminate getter/setters
- public boolean containsUserDeletions() {
- return mContainsUserDeletions;
- }
-
- // TODO: Refactor to eliminate getter/setters
- public void setCorrectionType(final int correctionType) {
- mCorrectionType = correctionType;
- }
-
- // TODO: Refactor to eliminate getter/setters
- public int getCorrectionType() {
- return mCorrectionType;
- }
-
- public boolean 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();
- // TODO: find time by binary search, e.g. using Collections#binarySearch()
- 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<>(laterLogStatements),
- new ArrayList<>(laterValues),
- new ArrayList<>(laterTimes),
- true /* isPartOfMegaword */);
- newLogUnit.mWords = null;
- newLogUnit.mMayContainDigit = mMayContainDigit;
- newLogUnit.mContainsUserDeletions = mContainsUserDeletions;
-
- // Purge the logStatements and associated data from this LogUnit.
- laterLogStatements.clear();
- laterValues.clear();
- laterTimes.clear();
- mIsPartOfMegaword = true;
-
- return newLogUnit;
- }
- }
- return new LogUnit();
- }
-
- public void append(final LogUnit logUnit) {
- mLogStatementList.addAll(logUnit.mLogStatementList);
- mValuesList.addAll(logUnit.mValuesList);
- mTimeList.addAll(logUnit.mTimeList);
- mWords = null;
- if (logUnit.mWords != null) {
- setWords(logUnit.mWords);
- }
- mMayContainDigit = mMayContainDigit || logUnit.mMayContainDigit;
- mContainsUserDeletions = mContainsUserDeletions || logUnit.mContainsUserDeletions;
- mIsPartOfMegaword = false;
- }
-
- public SuggestedWords getSuggestions() {
- return mSuggestedWords;
- }
-
- /**
- * Initialize the suggestions.
- *
- * Once set to a non-null value, the suggestions may not be changed again. This is to keep
- * track of the list of words that are close to the user's initial effort to type the word.
- * Only words that are close to the initial effort are considered typo corrections.
- */
- public void initializeSuggestions(final SuggestedWords suggestedWords) {
- if (mSuggestedWords == null) {
- mSuggestedWords = suggestedWords;
- }
- }
-
- private static boolean isInSuggestedWords(final String queryWord,
- final SuggestedWords suggestedWords) {
- if (TextUtils.isEmpty(queryWord)) {
- return false;
- }
- final int size = suggestedWords.size();
- for (int i = 0; i < size; i++) {
- final SuggestedWordInfo wordInfo = suggestedWords.getInfo(i);
- if (queryWord.equals(wordInfo.mWord)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Remove data associated with selecting the Research button.
- *
- * A LogUnit will capture all user interactions with the IME, including the "meta-interactions"
- * of using the Research button to control the logging (e.g. by starting and stopping recording
- * of a test case). Because meta-interactions should not be part of the normal log, calling
- * this method will set a field in the LogStatements of the motion events to indiciate that
- * they should be disregarded.
- *
- * This implementation assumes that the data recorded by the meta-interaction takes the
- * form of all events following the first MotionEvent.ACTION_DOWN before the first long-press
- * before the last onCodeEvent containing a code matching {@code LogStatement.VALUE_RESEARCH}.
- *
- * @returns true if data was removed
- */
- public boolean removeResearchButtonInvocation() {
- // This method is designed to be idempotent.
-
- // First, find last invocation of "research" key
- final int indexOfLastResearchKey = findLastIndexContainingKeyValue(
- LogStatement.TYPE_POINTER_TRACKER_CALL_LISTENER_ON_CODE_INPUT,
- LogStatement.KEY_CODE, LogStatement.VALUE_RESEARCH);
- if (indexOfLastResearchKey < 0) {
- // Could not find invocation of "research" key. Leave log as is.
- if (DEBUG) {
- Log.d(TAG, "Could not find research key");
- }
- return false;
- }
-
- // Look for the long press that started the invocation of the research key code input.
- final int indexOfLastLongPressBeforeResearchKey =
- findLastIndexBefore(LogStatement.TYPE_MAIN_KEYBOARD_VIEW_ON_LONG_PRESS,
- indexOfLastResearchKey);
-
- // Look for DOWN event preceding the long press
- final int indexOfLastDownEventBeforeLongPress =
- findLastIndexContainingKeyValueBefore(LogStatement.TYPE_MOTION_EVENT,
- LogStatement.ACTION, LogStatement.VALUE_DOWN,
- indexOfLastLongPressBeforeResearchKey);
-
- // Flag all LatinKeyboardViewProcessMotionEvents from the DOWN event to the research key as
- // logging-related
- final int startingIndex = indexOfLastDownEventBeforeLongPress == -1 ? 0
- : indexOfLastDownEventBeforeLongPress;
- for (int index = startingIndex; index < indexOfLastResearchKey; index++) {
- final LogStatement logStatement = mLogStatementList.get(index);
- final String type = logStatement.getType();
- final Object[] values = mValuesList.get(index);
- if (type.equals(LogStatement.TYPE_MOTION_EVENT)) {
- logStatement.setValue(LogStatement.KEY_IS_LOGGING_RELATED, values, true);
- }
- }
- return true;
- }
-
- /**
- * Find the index of the last LogStatement before {@code startingIndex} of type {@code type}.
- *
- * @param queryType a String that must be {@code String.equals()} to the LogStatement type
- * @param startingIndex the index to start the backward search from. Must be less than the
- * length of mLogStatementList, or an IndexOutOfBoundsException is thrown. Can be negative,
- * in which case -1 is returned.
- *
- * @return The index of the last LogStatement, -1 if none exists.
- */
- private int findLastIndexBefore(final String queryType, final int startingIndex) {
- return findLastIndexContainingKeyValueBefore(queryType, null, null, startingIndex);
- }
-
- /**
- * Find the index of the last LogStatement before {@code startingIndex} of type {@code type}
- * containing the given key-value pair.
- *
- * @param queryType a String that must be {@code String.equals()} to the LogStatement type
- * @param queryKey a String that must be {@code String.equals()} to a key in the LogStatement
- * @param queryValue an Object that must be {@code String.equals()} to the key's corresponding
- * value
- *
- * @return The index of the last LogStatement, -1 if none exists.
- */
- private int findLastIndexContainingKeyValue(final String queryType, final String queryKey,
- final Object queryValue) {
- return findLastIndexContainingKeyValueBefore(queryType, queryKey, queryValue,
- mLogStatementList.size() - 1);
- }
-
- /**
- * Find the index of the last LogStatement before {@code startingIndex} of type {@code type}
- * containing the given key-value pair.
- *
- * @param queryType a String that must be {@code String.equals()} to the LogStatement type
- * @param queryKey a String that must be {@code String.equals()} to a key in the LogStatement
- * @param queryValue an Object that must be {@code String.equals()} to the key's corresponding
- * value
- * @param startingIndex the index to start the backward search from. Must be less than the
- * length of mLogStatementList, or an IndexOutOfBoundsException is thrown. Can be negative,
- * in which case -1 is returned.
- *
- * @return The index of the last LogStatement, -1 if none exists.
- */
- private int findLastIndexContainingKeyValueBefore(final String queryType, final String queryKey,
- final Object queryValue, final int startingIndex) {
- if (startingIndex < 0) {
- return -1;
- }
- for (int index = startingIndex; index >= 0; index--) {
- final LogStatement logStatement = mLogStatementList.get(index);
- final String type = logStatement.getType();
- if (type.equals(queryType) && (queryKey == null
- || logStatement.containsKeyValuePair(queryKey, queryValue,
- mValuesList.get(index)))) {
- return index;
- }
- }
- return -1;
- }
-}
diff --git a/java/src/com/android/inputmethod/research/LoggingUtils.java b/java/src/com/android/inputmethod/research/LoggingUtils.java
deleted file mode 100644
index 1261d6780..000000000
--- a/java/src/com/android/inputmethod/research/LoggingUtils.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2013 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.view.MotionEvent;
-
-/* package */ class LoggingUtils {
- private LoggingUtils() {
- // This utility class is not publicly instantiable.
- }
-
- /* package */ static String getMotionEventActionTypeString(final int actionType) {
- switch (actionType) {
- case MotionEvent.ACTION_CANCEL: return "CANCEL";
- case MotionEvent.ACTION_UP: return "UP";
- case MotionEvent.ACTION_DOWN: return "DOWN";
- case MotionEvent.ACTION_POINTER_UP: return "POINTER_UP";
- case MotionEvent.ACTION_POINTER_DOWN: return "POINTER_DOWN";
- case MotionEvent.ACTION_MOVE: return "MOVE";
- case MotionEvent.ACTION_OUTSIDE: return "OUTSIDE";
- default: return "ACTION_" + actionType;
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
deleted file mode 100644
index 3806ac755..000000000
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * 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.util.Log;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.DictionaryFacilitator;
-import com.android.inputmethod.latin.define.ProductionFlag;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.LinkedList;
-
-/**
- * MainLogBuffer is a FixedLogBuffer that tracks the state of LogUnits to make privacy guarantees.
- *
- * There are three forms of privacy protection: 1) only words in the main dictionary are allowed to
- * be logged in enough detail to determine their contents, 2) only a subset of words are logged
- * in detail, such as 10%, and 3) no numbers are logged.
- *
- * This class maintains a list of LogUnits, each corresponding to a word. As the user completes
- * words, they are added here. But if the user backs up over their current word to edit a word
- * entered earlier, then it is pulled out of this LogBuffer, changes are then added to the end of
- * the LogUnit, and it is pushed back in here when the user is done. Because words may be pulled
- * back out even after they are pushed in, we must not publish the contents of this LogBuffer too
- * quickly. However, we cannot let the contents pile up either, or it will limit the editing that
- * a user can perform.
- *
- * To balance these requirements (keep history so user can edit, flush history so it does not pile
- * up), the LogBuffer is considered "complete" when the user has entered enough words to form an
- * n-gram, followed by enough additional non-detailed words (that are in the 90%, as per above).
- * Once complete, the n-gram may be published to flash storage (via the ResearchLog class).
- * However, the additional non-detailed words are retained, in case the user backspaces to edit
- * them. The MainLogBuffer then continues to add words, publishing individual non-detailed words
- * as new words arrive. After enough non-detailed words have been pushed out to account for the
- * 90% between words, the words at the front of the LogBuffer can be published as an n-gram again.
- *
- * If the words that would form the valid n-gram are not in the dictionary, then words are pushed
- * through the LogBuffer one at a time until an n-gram is found that is entirely composed of
- * dictionary words.
- *
- * If the user closes a session, then the entire LogBuffer is flushed, publishing any embedded
- * n-gram containing dictionary words.
- */
-public abstract class MainLogBuffer extends FixedLogBuffer {
- private static final String TAG = MainLogBuffer.class.getSimpleName();
- private static final boolean DEBUG = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
-
- // Keep consistent with switch statement in Statistics.recordPublishabilityResultCode()
- public static final int PUBLISHABILITY_PUBLISHABLE = 0;
- public static final int PUBLISHABILITY_UNPUBLISHABLE_STOPPING = 1;
- public static final int PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT = 2;
- public static final int PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY = 3;
- public static final int PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE = 4;
- public static final int PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT = 5;
- public static final int PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY = 6;
-
- // The size of the n-grams logged. E.g. N_GRAM_SIZE = 2 means to sample bigrams.
- public static final int N_GRAM_SIZE = 2;
-
- private final DictionaryFacilitator mDictionaryFacilitator;
- @UsedForTesting
- private Dictionary mDictionaryForTesting;
- private boolean mIsStopping = false;
-
- /* package for test */ int mNumWordsBetweenNGrams;
-
- // Counter for words left to suppress before an n-gram can be sampled. Reset to mMinWordPeriod
- // after a sample is taken.
- /* package for test */ int mNumWordsUntilSafeToSample;
-
- public MainLogBuffer(final int wordsBetweenSamples, final int numInitialWordsToIgnore,
- final DictionaryFacilitator dictionaryFacilitator) {
- super(N_GRAM_SIZE + wordsBetweenSamples);
- mNumWordsBetweenNGrams = wordsBetweenSamples;
- mNumWordsUntilSafeToSample = DEBUG ? 0 : numInitialWordsToIgnore;
- mDictionaryFacilitator = dictionaryFacilitator;
- }
-
- @UsedForTesting
- /* package for test */ void setDictionaryForTesting(final Dictionary dictionary) {
- mDictionaryForTesting = dictionary;
- }
-
- private boolean isValidDictWord(final String word) {
- if (mDictionaryForTesting != null) {
- return mDictionaryForTesting.isValidWord(word);
- }
- if (mDictionaryFacilitator != null) {
- return mDictionaryFacilitator.isValidMainDictWord(word);
- }
- return false;
- }
-
- public void setIsStopping() {
- mIsStopping = true;
- }
-
- /**
- * Determines whether the string determined by a series of LogUnits will not violate user
- * privacy if published.
- *
- * @param logUnits a LogUnit list to check for publishability
- * @param nGramSize the smallest n-gram acceptable to be published. if
- * {@link ResearchLogger#IS_LOGGING_EVERYTHING} is true, then publish if there are more than
- * {@code minNGramSize} words in the logUnits, otherwise wait. if {@link
- * ResearchLogger#IS_LOGGING_EVERYTHING} is false, then ensure that there are exactly nGramSize
- * words in the LogUnits.
- *
- * @return one of the {@code PUBLISHABILITY_*} result codes defined in this class.
- */
- private int getPublishabilityResultCode(final ArrayList<LogUnit> logUnits,
- final int nGramSize) {
- // Bypass privacy checks when debugging.
- if (ResearchLogger.IS_LOGGING_EVERYTHING) {
- if (mIsStopping) {
- return PUBLISHABILITY_UNPUBLISHABLE_STOPPING;
- }
- // Only check that it is the right length. If not, wait for later words to make
- // complete n-grams.
- int numWordsInLogUnitList = 0;
- final int length = logUnits.size();
- for (int i = 0; i < length; i++) {
- final LogUnit logUnit = logUnits.get(i);
- numWordsInLogUnitList += logUnit.getNumWords();
- }
- if (numWordsInLogUnitList >= nGramSize) {
- return PUBLISHABILITY_PUBLISHABLE;
- } else {
- return PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT;
- }
- }
-
- // Check that we are not sampling too frequently. Having sampled recently might disclose
- // too much of the user's intended meaning.
- if (mNumWordsUntilSafeToSample > 0) {
- return PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY;
- }
- // Reload the dictionary in case it has changed (e.g., because the user has changed
- // languages).
- if ((mDictionaryFacilitator == null
- || !mDictionaryFacilitator.hasInitializedMainDictionary())
- && mDictionaryForTesting == null) {
- // Main dictionary is unavailable. Since we cannot check it, we cannot tell if a
- // word is out-of-vocabulary or not. Therefore, we must judge the entire buffer
- // contents to potentially pose a privacy risk.
- return PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE;
- }
-
- // Check each word in the buffer. If any word poses a privacy threat, we cannot upload
- // the complete buffer contents in detail.
- int numWordsInLogUnitList = 0;
- for (final LogUnit logUnit : logUnits) {
- if (!logUnit.hasOneOrMoreWords()) {
- // Digits outside words are a privacy threat.
- if (logUnit.mayContainDigit()) {
- return PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT;
- }
- } else {
- numWordsInLogUnitList += logUnit.getNumWords();
- final String[] words = logUnit.getWordsAsStringArray();
- for (final String word : words) {
- // Words not in the dictionary are a privacy threat.
- if (ResearchLogger.hasLetters(word) && !isValidDictWord(word)) {
- if (DEBUG) {
- Log.d(TAG, "\"" + word + "\" NOT SAFE!: hasLetters: "
- + ResearchLogger.hasLetters(word)
- + ", isValid: " + isValidDictWord(word));
- }
- return PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY;
- }
- }
- }
- }
-
- // Finally, only return true if the ngram is the right size.
- if (numWordsInLogUnitList == nGramSize) {
- return PUBLISHABILITY_PUBLISHABLE;
- } else {
- return PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT;
- }
- }
-
- public void shiftAndPublishAll() throws IOException {
- final LinkedList<LogUnit> logUnits = getLogUnits();
- while (!logUnits.isEmpty()) {
- publishLogUnitsAtFrontOfBuffer();
- }
- }
-
- @Override
- protected final void onBufferFull() {
- try {
- publishLogUnitsAtFrontOfBuffer();
- } catch (final IOException e) {
- if (DEBUG) {
- Log.w(TAG, "IOException when publishing front of LogBuffer", e);
- }
- }
- }
-
- /**
- * If there is a safe n-gram at the front of this log buffer, publish it with all details, and
- * remove the LogUnits that constitute it.
- *
- * An n-gram might not be "safe" if it violates privacy controls. E.g., it might contain
- * numbers, an out-of-vocabulary word, or another n-gram may have been published recently. If
- * there is no safe n-gram, then the LogUnits up through the first word-containing LogUnit are
- * published, but without disclosing any privacy-related details, such as the word the LogUnit
- * generated, motion data, etc.
- *
- * Note that a LogUnit can hold more than one word if the user types without explicit spaces.
- * In this case, the words may be grouped together in such a way that pulling an n-gram off the
- * front would require splitting a LogUnit. Splitting a LogUnit is not possible, so this case
- * is treated just as the unsafe n-gram case. This may cause n-grams to be sampled at slightly
- * less than the target frequency.
- */
- protected final void publishLogUnitsAtFrontOfBuffer() throws IOException {
- // TODO: Refactor this method to require fewer passes through the LogUnits. Should really
- // require only one pass.
- ArrayList<LogUnit> logUnits = peekAtFirstNWords(N_GRAM_SIZE);
- final int publishabilityResultCode = getPublishabilityResultCode(logUnits, N_GRAM_SIZE);
- ResearchLogger.recordPublishabilityResultCode(publishabilityResultCode);
- if (publishabilityResultCode == MainLogBuffer.PUBLISHABILITY_PUBLISHABLE) {
- // Good n-gram at the front of the buffer. Publish it, disclosing details.
- publish(logUnits, true /* canIncludePrivateData */);
- shiftOutWords(N_GRAM_SIZE);
- mNumWordsUntilSafeToSample = mNumWordsBetweenNGrams;
- return;
- }
- // No good n-gram at front, and buffer is full. Shift out up through the first logUnit
- // with associated words (or if there is none, all the existing logUnits).
- logUnits.clear();
- LogUnit logUnit = shiftOut();
- while (logUnit != null) {
- logUnits.add(logUnit);
- final int numWords = logUnit.getNumWords();
- if (numWords > 0) {
- mNumWordsUntilSafeToSample = Math.max(0, mNumWordsUntilSafeToSample - numWords);
- break;
- }
- logUnit = shiftOut();
- }
- publish(logUnits, false /* canIncludePrivateData */);
- }
-
- /**
- * Called when a list of logUnits should be published.
- *
- * It is the subclass's responsibility to implement the publication.
- *
- * @param logUnits The list of logUnits to be published.
- * @param canIncludePrivateData Whether the private data in the logUnits can be included in
- * publication.
- *
- * @throws IOException if publication to the log file is not possible
- */
- protected abstract void publish(final ArrayList<LogUnit> logUnits,
- final boolean canIncludePrivateData) throws IOException;
-
- @Override
- protected int shiftOutWords(final int numWords) {
- final int numWordsShiftedOut = super.shiftOutWords(numWords);
- mNumWordsUntilSafeToSample = Math.max(0, mNumWordsUntilSafeToSample - numWordsShiftedOut);
- if (DEBUG) {
- Log.d(TAG, "wordsUntilSafeToSample now at " + mNumWordsUntilSafeToSample);
- }
- return numWordsShiftedOut;
- }
-}
diff --git a/java/src/com/android/inputmethod/research/MotionEventReader.java b/java/src/com/android/inputmethod/research/MotionEventReader.java
deleted file mode 100644
index f6b7352e7..000000000
--- a/java/src/com/android/inputmethod/research/MotionEventReader.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2013 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.util.JsonReader;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.MotionEvent.PointerCoords;
-import android.view.MotionEvent.PointerProperties;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.define.ProductionFlag;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-
-public class MotionEventReader {
- private static final String TAG = MotionEventReader.class.getSimpleName();
- private static final boolean DEBUG = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- // Assumes that MotionEvent.ACTION_MASK does not have all bits set.`
- private static final int UNINITIALIZED_ACTION = ~MotionEvent.ACTION_MASK;
- // No legitimate int is negative
- private static final int UNINITIALIZED_INT = -1;
- // No legitimate long is negative
- private static final long UNINITIALIZED_LONG = -1L;
- // No legitimate float is negative
- private static final float UNINITIALIZED_FLOAT = -1.0f;
-
- public ReplayData readMotionEventData(final File file) {
- final ReplayData replayData = new ReplayData();
- try {
- // Read file
- final JsonReader jsonReader = new JsonReader(new BufferedReader(new InputStreamReader(
- new FileInputStream(file))));
- jsonReader.beginArray();
- while (jsonReader.hasNext()) {
- readLogStatement(jsonReader, replayData);
- }
- jsonReader.endArray();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return replayData;
- }
-
- @UsedForTesting
- static class ReplayData {
- final ArrayList<Integer> mActions = new ArrayList<>();
- final ArrayList<PointerProperties[]> mPointerPropertiesArrays = new ArrayList<>();
- final ArrayList<PointerCoords[]> mPointerCoordsArrays = new ArrayList<>();
- final ArrayList<Long> mTimes = new ArrayList<>();
- }
-
- /**
- * Read motion data from a logStatement and store it in {@code replayData}.
- *
- * Two kinds of logStatements can be read. In the first variant, the MotionEvent data is
- * represented as attributes at the top level like so:
- *
- * <pre>
- * {
- * "_ct": 1359590400000,
- * "_ut": 4381933,
- * "_ty": "MotionEvent",
- * "action": "UP",
- * "isLoggingRelated": false,
- * "x": 100,
- * "y": 200
- * }
- * </pre>
- *
- * In the second variant, there is a separate attribute for the MotionEvent that includes
- * historical data if present:
- *
- * <pre>
- * {
- * "_ct": 135959040000,
- * "_ut": 4382702,
- * "_ty": "MotionEvent",
- * "action": "MOVE",
- * "isLoggingRelated": false,
- * "motionEvent": {
- * "pointerIds": [
- * 0
- * ],
- * "xyt": [
- * {
- * "t": 4382551,
- * "d": [
- * {
- * "x": 141.25,
- * "y": 151.8485107421875,
- * "toma": 101.82337188720703,
- * "tomi": 101.82337188720703,
- * "o": 0.0
- * }
- * ]
- * },
- * {
- * "t": 4382559,
- * "d": [
- * {
- * "x": 140.7266082763672,
- * "y": 151.8485107421875,
- * "toma": 101.82337188720703,
- * "tomi": 101.82337188720703,
- * "o": 0.0
- * }
- * ]
- * }
- * ]
- * }
- * },
- * </pre>
- */
- @UsedForTesting
- /* package for test */ void readLogStatement(final JsonReader jsonReader,
- final ReplayData replayData) throws IOException {
- String logStatementType = null;
- int actionType = UNINITIALIZED_ACTION;
- int x = UNINITIALIZED_INT;
- int y = UNINITIALIZED_INT;
- long time = UNINITIALIZED_LONG;
- boolean isLoggingRelated = false;
-
- jsonReader.beginObject();
- while (jsonReader.hasNext()) {
- final String key = jsonReader.nextName();
- if (key.equals("_ty")) {
- logStatementType = jsonReader.nextString();
- } else if (key.equals("_ut")) {
- time = jsonReader.nextLong();
- } else if (key.equals("x")) {
- x = jsonReader.nextInt();
- } else if (key.equals("y")) {
- y = jsonReader.nextInt();
- } else if (key.equals("action")) {
- final String s = jsonReader.nextString();
- if (s.equals("UP")) {
- actionType = MotionEvent.ACTION_UP;
- } else if (s.equals("DOWN")) {
- actionType = MotionEvent.ACTION_DOWN;
- } else if (s.equals("MOVE")) {
- actionType = MotionEvent.ACTION_MOVE;
- }
- } else if (key.equals("loggingRelated")) {
- isLoggingRelated = jsonReader.nextBoolean();
- } else if (logStatementType != null && logStatementType.equals("MotionEvent")
- && key.equals("motionEvent")) {
- if (actionType == UNINITIALIZED_ACTION) {
- Log.e(TAG, "no actionType assigned in MotionEvent json");
- }
- // Second variant of LogStatement.
- if (isLoggingRelated) {
- jsonReader.skipValue();
- } else {
- readEmbeddedMotionEvent(jsonReader, replayData, actionType);
- }
- } else {
- if (DEBUG) {
- Log.w(TAG, "Unknown JSON key in LogStatement: " + key);
- }
- jsonReader.skipValue();
- }
- }
- jsonReader.endObject();
-
- if (logStatementType != null && time != UNINITIALIZED_LONG && x != UNINITIALIZED_INT
- && y != UNINITIALIZED_INT && actionType != UNINITIALIZED_ACTION
- && logStatementType.equals("MotionEvent") && !isLoggingRelated) {
- // First variant of LogStatement.
- final PointerProperties pointerProperties = new PointerProperties();
- pointerProperties.id = 0;
- pointerProperties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
- final PointerProperties[] pointerPropertiesArray = {
- pointerProperties
- };
- final PointerCoords pointerCoords = new PointerCoords();
- pointerCoords.x = x;
- pointerCoords.y = y;
- pointerCoords.pressure = 1.0f;
- pointerCoords.size = 1.0f;
- final PointerCoords[] pointerCoordsArray = {
- pointerCoords
- };
- addMotionEventData(replayData, actionType, time, pointerPropertiesArray,
- pointerCoordsArray);
- }
- }
-
- private void readEmbeddedMotionEvent(final JsonReader jsonReader, final ReplayData replayData,
- final int actionType) throws IOException {
- jsonReader.beginObject();
- PointerProperties[] pointerPropertiesArray = null;
- while (jsonReader.hasNext()) { // pointerIds/xyt
- final String name = jsonReader.nextName();
- if (name.equals("pointerIds")) {
- pointerPropertiesArray = readPointerProperties(jsonReader);
- } else if (name.equals("xyt")) {
- readPointerData(jsonReader, replayData, actionType, pointerPropertiesArray);
- }
- }
- jsonReader.endObject();
- }
-
- private PointerProperties[] readPointerProperties(final JsonReader jsonReader)
- throws IOException {
- final ArrayList<PointerProperties> pointerPropertiesArrayList = new ArrayList<>();
- jsonReader.beginArray();
- while (jsonReader.hasNext()) {
- final PointerProperties pointerProperties = new PointerProperties();
- pointerProperties.id = jsonReader.nextInt();
- pointerProperties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
- pointerPropertiesArrayList.add(pointerProperties);
- }
- jsonReader.endArray();
- return pointerPropertiesArrayList.toArray(
- new PointerProperties[pointerPropertiesArrayList.size()]);
- }
-
- private void readPointerData(final JsonReader jsonReader, final ReplayData replayData,
- final int actionType, final PointerProperties[] pointerPropertiesArray)
- throws IOException {
- if (pointerPropertiesArray == null) {
- Log.e(TAG, "PointerIDs must be given before xyt data in json for MotionEvent");
- jsonReader.skipValue();
- return;
- }
- long time = UNINITIALIZED_LONG;
- jsonReader.beginArray();
- while (jsonReader.hasNext()) { // Array of historical data
- jsonReader.beginObject();
- final ArrayList<PointerCoords> pointerCoordsArrayList = new ArrayList<>();
- while (jsonReader.hasNext()) { // Time/data object
- final String name = jsonReader.nextName();
- if (name.equals("t")) {
- time = jsonReader.nextLong();
- } else if (name.equals("d")) {
- jsonReader.beginArray();
- while (jsonReader.hasNext()) { // array of data per pointer
- final PointerCoords pointerCoords = readPointerCoords(jsonReader);
- if (pointerCoords != null) {
- pointerCoordsArrayList.add(pointerCoords);
- }
- }
- jsonReader.endArray();
- } else {
- jsonReader.skipValue();
- }
- }
- jsonReader.endObject();
- // Data was recorded as historical events, but must be split apart into
- // separate MotionEvents for replaying
- if (time != UNINITIALIZED_LONG) {
- addMotionEventData(replayData, actionType, time, pointerPropertiesArray,
- pointerCoordsArrayList.toArray(
- new PointerCoords[pointerCoordsArrayList.size()]));
- } else {
- Log.e(TAG, "Time not assigned in json for MotionEvent");
- }
- }
- jsonReader.endArray();
- }
-
- private PointerCoords readPointerCoords(final JsonReader jsonReader) throws IOException {
- jsonReader.beginObject();
- float x = UNINITIALIZED_FLOAT;
- float y = UNINITIALIZED_FLOAT;
- while (jsonReader.hasNext()) { // x,y
- final String name = jsonReader.nextName();
- if (name.equals("x")) {
- x = (float) jsonReader.nextDouble();
- } else if (name.equals("y")) {
- y = (float) jsonReader.nextDouble();
- } else {
- jsonReader.skipValue();
- }
- }
- jsonReader.endObject();
-
- if (Float.compare(x, UNINITIALIZED_FLOAT) == 0
- || Float.compare(y, UNINITIALIZED_FLOAT) == 0) {
- Log.w(TAG, "missing x or y value in MotionEvent json");
- return null;
- }
- final PointerCoords pointerCoords = new PointerCoords();
- pointerCoords.x = x;
- pointerCoords.y = y;
- pointerCoords.pressure = 1.0f;
- pointerCoords.size = 1.0f;
- return pointerCoords;
- }
-
- private void addMotionEventData(final ReplayData replayData, final int actionType,
- final long time, final PointerProperties[] pointerProperties,
- final PointerCoords[] pointerCoords) {
- replayData.mActions.add(actionType);
- replayData.mTimes.add(time);
- replayData.mPointerPropertiesArrays.add(pointerProperties);
- replayData.mPointerCoordsArrays.add(pointerCoords);
- }
-}
diff --git a/java/src/com/android/inputmethod/research/Replayer.java b/java/src/com/android/inputmethod/research/Replayer.java
deleted file mode 100644
index 903875f3c..000000000
--- a/java/src/com/android/inputmethod/research/Replayer.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * 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.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.MotionEvent.PointerCoords;
-import android.view.MotionEvent.PointerProperties;
-
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.keyboard.MainKeyboardView;
-import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.research.MotionEventReader.ReplayData;
-
-/**
- * Replays a sequence of motion events in realtime on the screen.
- *
- * Useful for user inspection of logged data.
- */
-public class Replayer {
- private static final String TAG = Replayer.class.getSimpleName();
- private static final boolean DEBUG = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- private static final long START_TIME_DELAY_MS = 500;
-
- private boolean mIsReplaying = false;
- private KeyboardSwitcher mKeyboardSwitcher;
-
- private Replayer() {
- }
-
- private static final Replayer sInstance = new Replayer();
- public static Replayer getInstance() {
- return sInstance;
- }
-
- public void setKeyboardSwitcher(final KeyboardSwitcher keyboardSwitcher) {
- mKeyboardSwitcher = keyboardSwitcher;
- }
-
- private static final int MSG_MOTION_EVENT = 0;
- private static final int MSG_DONE = 1;
- private static final int COMPLETION_TIME_MS = 500;
-
- // TODO: Support historical events and multi-touch.
- public void replay(final ReplayData replayData, final Runnable callback) {
- if (mIsReplaying) {
- return;
- }
- mIsReplaying = true;
- final int numActions = replayData.mActions.size();
- if (DEBUG) {
- Log.d(TAG, "replaying " + numActions + " actions");
- }
- if (numActions == 0) {
- mIsReplaying = false;
- return;
- }
- final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
-
- // The reference time relative to the times stored in events.
- final long origStartTime = replayData.mTimes.get(0);
- // The reference time relative to which events are replayed in the present.
- final long currentStartTime = SystemClock.uptimeMillis() + START_TIME_DELAY_MS;
- // The adjustment needed to translate times from the original recorded time to the current
- // time.
- final long timeAdjustment = currentStartTime - origStartTime;
- final Handler handler = new Handler(Looper.getMainLooper()) {
- // Track the time of the most recent DOWN event, to be passed as a parameter when
- // constructing a MotionEvent. It's initialized here to the origStartTime, but this is
- // only a precaution. The value should be overwritten by the first ACTION_DOWN event
- // before the first use of the variable. Note that this may cause the first few events
- // to have incorrect {@code downTime}s.
- private long mOrigDownTime = origStartTime;
-
- @Override
- public void handleMessage(final Message msg) {
- switch (msg.what) {
- case MSG_MOTION_EVENT:
- final int index = msg.arg1;
- final int action = replayData.mActions.get(index);
- final PointerProperties[] pointerPropertiesArray =
- replayData.mPointerPropertiesArrays.get(index);
- final PointerCoords[] pointerCoordsArray =
- replayData.mPointerCoordsArrays.get(index);
- final long origTime = replayData.mTimes.get(index);
- if (action == MotionEvent.ACTION_DOWN) {
- mOrigDownTime = origTime;
- }
-
- final MotionEvent me = MotionEvent.obtain(mOrigDownTime + timeAdjustment,
- origTime + timeAdjustment, action,
- pointerPropertiesArray.length, pointerPropertiesArray,
- pointerCoordsArray, 0, 0, 1.0f, 1.0f, 0, 0, 0, 0);
- mainKeyboardView.processMotionEvent(me);
- me.recycle();
- break;
- case MSG_DONE:
- mIsReplaying = false;
- ResearchLogger.getInstance().requestIndicatorRedraw();
- break;
- }
- }
- };
-
- handler.post(new Runnable() {
- @Override
- public void run() {
- ResearchLogger.getInstance().requestIndicatorRedraw();
- }
- });
- for (int i = 0; i < numActions; i++) {
- final Message msg = Message.obtain(handler, MSG_MOTION_EVENT, i, 0);
- final long msgTime = replayData.mTimes.get(i) + timeAdjustment;
- handler.sendMessageAtTime(msg, msgTime);
- if (DEBUG) {
- Log.d(TAG, "queuing event at " + msgTime);
- }
- }
-
- final long presentDoneTime = replayData.mTimes.get(numActions - 1) + timeAdjustment
- + COMPLETION_TIME_MS;
- handler.sendMessageAtTime(Message.obtain(handler, MSG_DONE), presentDoneTime);
- if (callback != null) {
- handler.postAtTime(callback, presentDoneTime + 1);
- }
- }
-
- public boolean isReplaying() {
- return mIsReplaying;
- }
-}
diff --git a/java/src/com/android/inputmethod/research/ReplayerService.java b/java/src/com/android/inputmethod/research/ReplayerService.java
deleted file mode 100644
index 88d9033cf..000000000
--- a/java/src/com/android/inputmethod/research/ReplayerService.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.app.IntentService;
-import android.content.Intent;
-import android.util.Log;
-
-import com.android.inputmethod.research.MotionEventReader.ReplayData;
-
-import java.io.File;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Provide a mechanism to invoke the replayer from outside.
- *
- * In particular, makes access from a host possible through {@code adb am startservice}.
- */
-public class ReplayerService extends IntentService {
- private static final String TAG = ReplayerService.class.getSimpleName();
- private static final String EXTRA_FILENAME = "com.android.inputmethod.research.extra.FILENAME";
- private static final long MAX_REPLAY_TIME = TimeUnit.SECONDS.toMillis(60);
-
- public ReplayerService() {
- super(ReplayerService.class.getSimpleName());
- }
-
- @Override
- protected void onHandleIntent(final Intent intent) {
- final String filename = intent.getStringExtra(EXTRA_FILENAME);
- if (filename == null) return;
-
- final ReplayData replayData = new MotionEventReader().readMotionEventData(
- new File(filename));
- synchronized (this) {
- Replayer.getInstance().replay(replayData, new Runnable() {
- @Override
- public void run() {
- synchronized (ReplayerService.this) {
- ReplayerService.this.notify();
- }
- }
- });
- try {
- wait(MAX_REPLAY_TIME);
- } catch (InterruptedException e) {
- Log.e(TAG, "Timeout while replaying.", e);
- }
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
deleted file mode 100644
index 46e620ae5..000000000
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * 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.Context;
-import android.util.JsonWriter;
-import android.util.Log;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.define.ProductionFlag;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executors;
-import java.util.concurrent.RejectedExecutionException;
-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
- * written to a {@link JsonWriter}, which will write to a local file.
- *
- * The JsonWriter is created on-demand by calling {@link #getInitializedJsonWriterLocked}.
- *
- * This class uses an executor to perform file-writing operations on a separate thread. It also
- * tries to avoid creating unnecessary files if there is nothing to write. It also handles
- * flushing, making sure it happens, but not too frequently.
- *
- * This functionality is off by default. See
- * {@link ProductionFlag#USES_DEVELOPMENT_ONLY_DIAGNOSTICS}.
- */
-public class ResearchLog {
- // TODO: Automatically initialize the JsonWriter rather than requiring the caller to manage it.
- private static final String TAG = ResearchLog.class.getSimpleName();
- private static final boolean DEBUG = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- private static final long FLUSH_DELAY_IN_MS = TimeUnit.SECONDS.toMillis(5);
-
- /* package */ final ScheduledExecutorService mExecutor;
- /* package */ final File mFile;
- private final Context mContext;
-
- // Earlier implementations used a dummy JsonWriter that just swallowed what it was given, but
- // this was tricky to do well, because JsonWriter throws an exception if it is passed more than
- // one top-level object.
- private JsonWriter mJsonWriter = null;
-
- // true if at least one byte of data has been written out to the log file. This must be
- // remembered because JsonWriter requires that calls matching calls to beginObject and
- // endObject, as well as beginArray and endArray, and the file is opened lazily, only when
- // it is certain that data will be written. Alternatively, the matching call exceptions
- // could be caught, but this might suppress other errors.
- private boolean mHasWrittenData = false;
-
- public ResearchLog(final File outputFile, final Context context) {
- mExecutor = Executors.newSingleThreadScheduledExecutor();
- mFile = outputFile;
- mContext = context;
- }
-
- /**
- * Returns true if this is a FeedbackLog.
- *
- * FeedbackLogs record only the data associated with a Feedback dialog. Instead of normal
- * logging, they contain a LogStatement with the complete feedback string and optionally a
- * recording of the user's supplied demo of the problem.
- */
- public boolean isFeedbackLog() {
- return false;
- }
-
- /**
- * Waits for any publication requests to finish and closes the {@link JsonWriter} used for
- * output.
- *
- * See class comment for details about {@code JsonWriter} construction.
- *
- * @param onClosed run after the close() operation has completed asynchronously
- */
- private synchronized void close(final Runnable onClosed) {
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- try {
- if (mJsonWriter == null) return null;
- // TODO: This is necessary to avoid an exception. Better would be to not even
- // open the JsonWriter if the file is not even opened unless there is valid data
- // to write.
- if (!mHasWrittenData) {
- mJsonWriter.beginArray();
- }
- mJsonWriter.endArray();
- mHasWrittenData = false;
- mJsonWriter.flush();
- mJsonWriter.close();
- if (DEBUG) {
- Log.d(TAG, "closed " + mFile);
- }
- } catch (final Exception e) {
- Log.d(TAG, "error when closing ResearchLog:", e);
- } finally {
- // Marking the file as read-only signals that this log file is ready to be
- // uploaded.
- if (mFile != null && mFile.exists()) {
- mFile.setWritable(false, false);
- }
- if (onClosed != null) {
- onClosed.run();
- }
- }
- return null;
- }
- });
- removeAnyScheduledFlush();
- mExecutor.shutdown();
- }
-
- /**
- * Block until the research log has shut down and spooled out all output or {@code timeout}
- * occurs.
- *
- * @param timeout time to wait for close in milliseconds
- */
- public void blockingClose(final long timeout) {
- close(null);
- awaitTermination(timeout, TimeUnit.MILLISECONDS);
- }
-
- /**
- * Waits for publication requests to finish, closes the JsonWriter, but then deletes the backing
- * output file.
- *
- * @param onAbort run after the abort() operation has completed asynchronously
- */
- private synchronized void abort(final Runnable onAbort) {
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- try {
- if (mJsonWriter == null) return null;
- if (mHasWrittenData) {
- // TODO: This is necessary to avoid an exception. Better would be to not
- // even open the JsonWriter if the file is not even opened unless there is
- // valid data to write.
- if (!mHasWrittenData) {
- mJsonWriter.beginArray();
- }
- mJsonWriter.endArray();
- mJsonWriter.close();
- mHasWrittenData = false;
- }
- } finally {
- if (mFile != null) {
- mFile.delete();
- }
- if (onAbort != null) {
- onAbort.run();
- }
- }
- return null;
- }
- });
- removeAnyScheduledFlush();
- mExecutor.shutdown();
- }
-
- /**
- * Block until the research log has aborted or {@code timeout} occurs.
- *
- * @param timeout time to wait for close in milliseconds
- */
- public void blockingAbort(final long timeout) {
- abort(null);
- awaitTermination(timeout, TimeUnit.MILLISECONDS);
- }
-
- @UsedForTesting
- public void awaitTermination(final long delay, final TimeUnit timeUnit) {
- try {
- if (!mExecutor.awaitTermination(delay, timeUnit)) {
- Log.e(TAG, "ResearchLog executor timed out while awaiting terminaion");
- }
- } catch (final InterruptedException e) {
- Log.e(TAG, "ResearchLog executor interrupted while awaiting terminaion", e);
- }
- }
-
- /* package */ synchronized void flush() {
- removeAnyScheduledFlush();
- mExecutor.submit(mFlushCallable);
- }
-
- private final Callable<Object> mFlushCallable = new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- if (mJsonWriter != null) 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);
- }
-
- /**
- * Queues up {@code logUnit} to be published in the background.
- *
- * @param logUnit the {@link LogUnit} to be published
- * @param canIncludePrivateData whether private data in the LogUnit should be included
- */
- public synchronized void publish(final LogUnit logUnit, final boolean canIncludePrivateData) {
- try {
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- logUnit.publishTo(ResearchLog.this, canIncludePrivateData);
- scheduleFlush();
- return null;
- }
- });
- } catch (final RejectedExecutionException e) {
- // TODO: Add code to record loss of data, and report.
- if (DEBUG) {
- Log.d(TAG, "ResearchLog.publish() rejecting scheduled execution", e);
- }
- }
- }
-
- /**
- * Return a JsonWriter for this ResearchLog. It is initialized the first time this method is
- * called. The cached value is returned in future calls.
- *
- * @throws IOException if opening the JsonWriter is not possible
- */
- public JsonWriter getInitializedJsonWriterLocked() throws IOException {
- if (mJsonWriter != null) return mJsonWriter;
- if (mFile == null) throw new FileNotFoundException();
- try {
- final JsonWriter jsonWriter = createJsonWriter(mContext, mFile);
- if (jsonWriter == null) throw new IOException("Could not create JsonWriter");
-
- jsonWriter.beginArray();
- mJsonWriter = jsonWriter;
- mHasWrittenData = true;
- return mJsonWriter;
- } catch (final IOException e) {
- if (DEBUG) {
- Log.w(TAG, "Exception when creating JsonWriter", e);
- Log.w(TAG, "Closing JsonWriter");
- }
- if (mJsonWriter != null) mJsonWriter.close();
- mJsonWriter = null;
- throw e;
- }
- }
-
- /**
- * Create the JsonWriter to write the ResearchLog to.
- *
- * This method may be overriden in testing to redirect the output.
- */
- /* package for test */ JsonWriter createJsonWriter(final Context context, final File file)
- throws IOException {
- return new JsonWriter(new BufferedWriter(new OutputStreamWriter(
- context.openFileOutput(file.getName(), Context.MODE_PRIVATE))));
- }
-}
diff --git a/java/src/com/android/inputmethod/research/ResearchLogDirectory.java b/java/src/com/android/inputmethod/research/ResearchLogDirectory.java
deleted file mode 100644
index d156068d6..000000000
--- a/java/src/com/android/inputmethod/research/ResearchLogDirectory.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2013 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.Context;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileFilter;
-
-/**
- * Manages log files.
- *
- * This class handles all aspects where and how research log data is stored. This includes
- * generating log filenames in the correct place with the correct names, and cleaning up log files
- * under this directory.
- */
-public class ResearchLogDirectory {
- public static final String TAG = ResearchLogDirectory.class.getSimpleName();
- /* package */ static final String LOG_FILENAME_PREFIX = "researchLog";
- private static final String FILENAME_SUFFIX = ".txt";
- private static final String USER_RECORDING_FILENAME_PREFIX = "recording";
-
- private static final ReadOnlyLogFileFilter sUploadableLogFileFilter =
- new ReadOnlyLogFileFilter();
-
- private final File mFilesDir;
-
- static class ReadOnlyLogFileFilter implements FileFilter {
- @Override
- public boolean accept(final File pathname) {
- return pathname.getName().startsWith(ResearchLogDirectory.LOG_FILENAME_PREFIX)
- && !pathname.canWrite();
- }
- }
-
- /**
- * Creates a new ResearchLogDirectory, creating the storage directory if it does not exist.
- */
- public ResearchLogDirectory(final Context context) {
- mFilesDir = getLoggingDirectory(context);
- if (mFilesDir == null) {
- throw new NullPointerException("No files directory specified");
- }
- if (!mFilesDir.exists()) {
- mFilesDir.mkdirs();
- }
- }
-
- private File getLoggingDirectory(final Context context) {
- // TODO: Switch to using a subdirectory of getFilesDir().
- return context.getFilesDir();
- }
-
- /**
- * Get an array of log files that are ready for uploading.
- *
- * A file is ready for uploading if it is marked as read-only.
- *
- * @return the array of uploadable files
- */
- public File[] getUploadableLogFiles() {
- try {
- return mFilesDir.listFiles(sUploadableLogFileFilter);
- } catch (final SecurityException e) {
- Log.e(TAG, "Could not cleanup log directory, permission denied", e);
- return new File[0];
- }
- }
-
- public void cleanupLogFilesOlderThan(final long time) {
- try {
- for (final File file : mFilesDir.listFiles()) {
- final String filename = file.getName();
- if ((filename.startsWith(LOG_FILENAME_PREFIX)
- || filename.startsWith(USER_RECORDING_FILENAME_PREFIX))
- && (file.lastModified() < time)) {
- file.delete();
- }
- }
- } catch (final SecurityException e) {
- Log.e(TAG, "Could not cleanup log directory, permission denied", e);
- }
- }
-
- public File getLogFilePath(final long time, final long nanoTime) {
- return new File(mFilesDir, getUniqueFilename(LOG_FILENAME_PREFIX, time, nanoTime));
- }
-
- public File getUserRecordingFilePath(final long time, final long nanoTime) {
- return new File(mFilesDir, getUniqueFilename(USER_RECORDING_FILENAME_PREFIX, time,
- nanoTime));
- }
-
- private static String getUniqueFilename(final String prefix, final long time,
- final long nanoTime) {
- return prefix + "-" + time + "-" + nanoTime + FILENAME_SUFFIX;
- }
-}
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
deleted file mode 100644
index d73f9c41c..000000000
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ /dev/null
@@ -1,1885 +0,0 @@
-/*
- * 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 static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.SystemClock;
-import android.preference.PreferenceManager;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.widget.Toast;
-
-import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.keyboard.KeyboardView;
-import com.android.inputmethod.keyboard.MainKeyboardView;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.DictionaryFacilitator;
-import com.android.inputmethod.latin.LatinIME;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.RichInputConnection;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.latin.utils.InputTypeUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
-import com.android.inputmethod.latin.utils.TextRange;
-import com.android.inputmethod.research.MotionEventReader.ReplayData;
-import com.android.inputmethod.research.ui.SplashScreen;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Pattern;
-
-// TODO: Add a unit test for every "logging" method (i.e. that is called from the IME and calls
-// enqueueEvent to record a LogStatement).
-/**
- * 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#USES_DEVELOPMENT_ONLY_DIAGNOSTICS}.
- */
-public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener,
- SplashScreen.UserConsentListener {
- // TODO: This class has grown quite large and combines several concerns that should be
- // separated. The following refactorings will be applied as soon as possible after adding
- // support for replaying historical events, fixing some replay bugs, adding some ui constraints
- // on the feedback dialog, and adding the survey dialog.
- // TODO: Refactor. Move feedback screen code into separate class.
- // TODO: Refactor. Move logging invocations into their own class.
- // TODO: Refactor. Move currentLogUnit management into separate class.
- private static final String TAG = ResearchLogger.class.getSimpleName();
- private static final boolean DEBUG = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- private static final boolean DEBUG_REPLAY_AFTER_FEEDBACK = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- // Whether the feedback dialog preserves the editable text across invocations. Should be false
- // for normal research builds so users do not have to delete the same feedback string they
- // entered earlier. Should be true for builds internal to a development team so when the text
- // field holds a channel name, the developer does not have to re-enter it when using the
- // feedback mechanism to generate multiple tests.
- private static final boolean FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD = false;
- /* package */ static boolean sIsLogging = false;
- private static final int OUTPUT_FORMAT_VERSION = 6;
- // Whether all words should be recorded, leaving unsampled word between bigrams. Useful for
- // testing.
- /* package for test */ static final boolean IS_LOGGING_EVERYTHING = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- // The number of words between n-grams to omit from the log.
- private static final int NUMBER_OF_WORDS_BETWEEN_SAMPLES =
- IS_LOGGING_EVERYTHING ? 0 : (DEBUG ? 2 : 18);
-
- // 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;
- // 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 ||
- (IS_LOGGING_EVERYTHING && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG);
- // FEEDBACK_WORD_BUFFER_SIZE should add 1 because it must also hold the feedback LogUnit itself.
- public static final int FEEDBACK_WORD_BUFFER_SIZE = (Integer.MAX_VALUE - 1) + 1;
-
- // The special output text to invoke a research feedback dialog.
- public static final String RESEARCH_KEY_OUTPUT_TEXT = ".research.";
-
- // constants related to specific log points
- private static final int[] WHITESPACE_SEPARATORS =
- StringUtils.toSortedCodePointArray(" \t\n\r");
- private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1
- private static final String PREF_RESEARCH_SAVED_CHANNEL = "pref_research_saved_channel";
-
- private static final long RESEARCHLOG_CLOSE_TIMEOUT_IN_MS = TimeUnit.SECONDS.toMillis(5);
- private static final long RESEARCHLOG_ABORT_TIMEOUT_IN_MS = TimeUnit.SECONDS.toMillis(5);
- private static final long DURATION_BETWEEN_DIR_CLEANUP_IN_MS = TimeUnit.DAYS.toMillis(1);
- private static final long MAX_LOGFILE_AGE_IN_MS = TimeUnit.DAYS.toMillis(4);
-
- private static final ResearchLogger sInstance = new ResearchLogger();
- private static String sAccountType = null;
- private static String sAllowedAccountDomain = null;
- private ResearchLog mMainResearchLog; // always non-null after init() is called
- // mFeedbackLog records all events for the session, private or not (excepting
- // passwords). It is written to permanent storage only if the user explicitly commands
- // the system to do so.
- // LogUnits are queued in the LogBuffers and published to the ResearchLogs when words are
- // complete.
- /* package for test */ MainLogBuffer mMainLogBuffer; // always non-null after init() is called
- /* package */ ResearchLog mUserRecordingLog;
- /* package */ LogBuffer mUserRecordingLogBuffer;
- private File mUserRecordingFile = null;
-
- private boolean mIsPasswordView = false;
- private SharedPreferences mPrefs;
-
- // digits entered by the user are replaced with this codepoint.
- /* package for test */ static final int DIGIT_REPLACEMENT_CODEPOINT =
- Character.codePointAt("\uE000", 0); // U+E000 is in the "private-use area"
- // U+E001 is in the "private-use area"
- /* package for test */ static final String WORD_REPLACEMENT_STRING = "\uE001";
- protected static final int SUSPEND_DURATION_IN_MINUTES = 1;
-
- // used to check whether words are not unique
- private DictionaryFacilitator mDictionaryFacilitator;
- private MainKeyboardView mMainKeyboardView;
- // TODO: Check whether a superclass can be used instead of LatinIME.
- /* package for test */ LatinIME mLatinIME;
- private final Statistics mStatistics;
- private final MotionEventReader mMotionEventReader = new MotionEventReader();
- private final Replayer mReplayer = Replayer.getInstance();
- private ResearchLogDirectory mResearchLogDirectory;
- private SplashScreen mSplashScreen;
-
- private Intent mUploadNowIntent;
-
- /* package for test */ LogUnit mCurrentLogUnit = new LogUnit();
-
- // Gestured or tapped words may be committed after the gesture of the next word has started.
- // To ensure that the gesture data of the next word is not associated with the previous word,
- // thereby leaking private data, we store the time of the down event that started the second
- // gesture, and when committing the earlier word, split the LogUnit.
- private long mSavedDownEventTime;
- private Bundle mFeedbackDialogBundle = null;
- // Whether the feedback dialog is visible, and the user is typing into it. Normal logging is
- // not performed on text that the user types into the feedback dialog.
- private boolean mInFeedbackDialog = false;
- private Handler mUserRecordingTimeoutHandler;
- private static final long USER_RECORDING_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30);
-
- // Stores a temporary LogUnit while generating a phantom space. Needed because phantom spaces
- // are issued out-of-order, immediately before the characters generated by other operations that
- // have already outputted LogStatements.
- private LogUnit mPhantomSpaceLogUnit = null;
-
- private ResearchLogger() {
- mStatistics = Statistics.getInstance();
- }
-
- public static ResearchLogger getInstance() {
- return sInstance;
- }
-
- public void init(final LatinIME latinIME, final KeyboardSwitcher keyboardSwitcher) {
- assert latinIME != null;
- mLatinIME = latinIME;
- mPrefs = PreferenceManager.getDefaultSharedPreferences(latinIME);
- mPrefs.registerOnSharedPreferenceChangeListener(this);
-
- // Initialize fields from preferences
- sIsLogging = ResearchSettings.readResearchLoggerEnabledFlag(mPrefs);
-
- // Initialize fields from resources
- final Resources res = latinIME.getResources();
- sAccountType = res.getString(R.string.research_account_type);
- sAllowedAccountDomain = res.getString(R.string.research_allowed_account_domain);
-
- // Initialize directory manager
- mResearchLogDirectory = new ResearchLogDirectory(mLatinIME);
- cleanLogDirectoryIfNeeded(mResearchLogDirectory, System.currentTimeMillis());
-
- // Initialize log buffers
- resetLogBuffers();
-
- // Initialize external services
- mUploadNowIntent = new Intent(mLatinIME, UploaderService.class);
- mUploadNowIntent.putExtra(UploaderService.EXTRA_UPLOAD_UNCONDITIONALLY, true);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- UploaderService.cancelAndRescheduleUploadingService(mLatinIME,
- true /* needsRescheduling */);
- }
- mReplayer.setKeyboardSwitcher(keyboardSwitcher);
- }
-
- private void resetLogBuffers() {
- mMainResearchLog = new ResearchLog(mResearchLogDirectory.getLogFilePath(
- System.currentTimeMillis(), System.nanoTime()), mLatinIME);
- final int numWordsToIgnore = new Random().nextInt(NUMBER_OF_WORDS_BETWEEN_SAMPLES + 1);
- mMainLogBuffer = new MainLogBuffer(NUMBER_OF_WORDS_BETWEEN_SAMPLES, numWordsToIgnore,
- mDictionaryFacilitator) {
- @Override
- protected void publish(final ArrayList<LogUnit> logUnits,
- boolean canIncludePrivateData) {
- canIncludePrivateData |= IS_LOGGING_EVERYTHING;
- for (final LogUnit logUnit : logUnits) {
- if (DEBUG) {
- final String wordsString = logUnit.getWordsAsString();
- Log.d(TAG, "onPublish: '" + wordsString
- + "', hc: " + logUnit.containsUserDeletions()
- + ", cipd: " + canIncludePrivateData);
- }
- for (final String word : logUnit.getWordsAsStringArray()) {
- final boolean isDictionaryWord = mDictionaryFacilitator != null
- && mDictionaryFacilitator.isValidMainDictWord(word);
- mStatistics.recordWordEntered(
- isDictionaryWord, logUnit.containsUserDeletions());
- }
- }
- publishLogUnits(logUnits, mMainResearchLog, canIncludePrivateData);
- }
- };
- }
-
- private void cleanLogDirectoryIfNeeded(final ResearchLogDirectory researchLogDirectory,
- final long now) {
- final long lastCleanupTime = ResearchSettings.readResearchLastDirCleanupTime(mPrefs);
- if (now - lastCleanupTime < DURATION_BETWEEN_DIR_CLEANUP_IN_MS) return;
- final long oldestAllowedFileTime = now - MAX_LOGFILE_AGE_IN_MS;
- mResearchLogDirectory.cleanupLogFilesOlderThan(oldestAllowedFileTime);
- ResearchSettings.writeResearchLastDirCleanupTime(mPrefs, now);
- }
-
- public void mainKeyboardView_onAttachedToWindow(final MainKeyboardView mainKeyboardView) {
- mMainKeyboardView = mainKeyboardView;
- maybeShowSplashScreen();
- }
-
- public void mainKeyboardView_onDetachedFromWindow() {
- mMainKeyboardView = null;
- }
-
- public void onDestroy() {
- if (mPrefs != null) {
- mPrefs.unregisterOnSharedPreferenceChangeListener(this);
- }
- }
-
- private void maybeShowSplashScreen() {
- if (ResearchSettings.readHasSeenSplash(mPrefs)) return;
- if (mSplashScreen != null && mSplashScreen.isShowing()) return;
- if (mMainKeyboardView == null) return;
- final IBinder windowToken = mMainKeyboardView.getWindowToken();
- if (windowToken == null) return;
-
- mSplashScreen = new SplashScreen(mLatinIME, this);
- mSplashScreen.showSplashScreen(windowToken);
- }
-
- @Override
- public void onSplashScreenUserClickedOk() {
- if (mPrefs == null) {
- mPrefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME);
- if (mPrefs == null) return;
- }
- sIsLogging = true;
- ResearchSettings.writeResearchLoggerEnabledFlag(mPrefs, true);
- ResearchSettings.writeHasSeenSplash(mPrefs, true);
- restart();
- }
-
- private void checkForEmptyEditor() {
- if (mLatinIME == null) {
- return;
- }
- final InputConnection ic = mLatinIME.getCurrentInputConnection();
- if (ic == null) {
- return;
- }
- final CharSequence textBefore = ic.getTextBeforeCursor(1, 0);
- if (!TextUtils.isEmpty(textBefore)) {
- mStatistics.setIsEmptyUponStarting(false);
- return;
- }
- final CharSequence textAfter = ic.getTextAfterCursor(1, 0);
- if (!TextUtils.isEmpty(textAfter)) {
- mStatistics.setIsEmptyUponStarting(false);
- return;
- }
- if (textBefore != null && textAfter != null) {
- mStatistics.setIsEmptyUponStarting(true);
- }
- }
-
- private void start() {
- if (DEBUG) {
- Log.d(TAG, "start called");
- }
- maybeShowSplashScreen();
- requestIndicatorRedraw();
- mStatistics.reset();
- checkForEmptyEditor();
- }
-
- /* package */ void stop() {
- if (DEBUG) {
- Log.d(TAG, "stop called");
- }
- // Commit mCurrentLogUnit before closing.
- commitCurrentLogUnit();
-
- try {
- mMainLogBuffer.shiftAndPublishAll();
- } catch (final IOException e) {
- Log.w(TAG, "IOException when publishing LogBuffer", e);
- }
- logStatistics();
- commitCurrentLogUnit();
- mMainLogBuffer.setIsStopping();
- try {
- mMainLogBuffer.shiftAndPublishAll();
- } catch (final IOException e) {
- Log.w(TAG, "IOException when publishing LogBuffer", e);
- }
- mMainResearchLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
-
- resetLogBuffers();
- cancelFeedbackDialog();
- }
-
- public void abort() {
- if (DEBUG) {
- Log.d(TAG, "abort called");
- }
- mMainLogBuffer.clear();
- mMainResearchLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS);
-
- resetLogBuffers();
- }
-
- private void restart() {
- stop();
- start();
- }
-
- @Override
- public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
- if (key == null || prefs == null) {
- return;
- }
- requestIndicatorRedraw();
- mPrefs = prefs;
- prefsChanged(prefs);
- }
-
- public void onResearchKeySelected(final LatinIME latinIME) {
- mCurrentLogUnit.removeResearchButtonInvocation();
- if (mInFeedbackDialog) {
- Toast.makeText(latinIME, R.string.research_please_exit_feedback_form,
- Toast.LENGTH_LONG).show();
- return;
- }
- presentFeedbackDialog(latinIME);
- }
-
- public void presentFeedbackDialogFromSettings() {
- if (mLatinIME != null) {
- presentFeedbackDialog(mLatinIME);
- }
- }
-
- public void presentFeedbackDialog(final LatinIME latinIME) {
- if (isMakingUserRecording()) {
- saveRecording();
- }
- mInFeedbackDialog = true;
-
- final Intent intent = new Intent();
- intent.setClass(mLatinIME, FeedbackActivity.class);
- if (mFeedbackDialogBundle == null) {
- // Restore feedback field with channel name
- final Bundle bundle = new Bundle();
- bundle.putBoolean(FeedbackFragment.KEY_INCLUDE_ACCOUNT_NAME, true);
- bundle.putBoolean(FeedbackFragment.KEY_HAS_USER_RECORDING, false);
- if (FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD) {
- final String savedChannelName = mPrefs.getString(PREF_RESEARCH_SAVED_CHANNEL, "");
- bundle.putString(FeedbackFragment.KEY_FEEDBACK_STRING, savedChannelName);
- }
- mFeedbackDialogBundle = bundle;
- }
- intent.putExtras(mFeedbackDialogBundle);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- latinIME.startActivity(intent);
- }
-
- public void setFeedbackDialogBundle(final Bundle bundle) {
- mFeedbackDialogBundle = bundle;
- }
-
- public void startRecording() {
- final Resources res = mLatinIME.getResources();
- Toast.makeText(mLatinIME,
- res.getString(R.string.research_feedback_demonstration_instructions),
- Toast.LENGTH_LONG).show();
- startRecordingInternal();
- }
-
- private void startRecordingInternal() {
- if (mUserRecordingLog != null) {
- mUserRecordingLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS);
- }
- mUserRecordingFile = mResearchLogDirectory.getUserRecordingFilePath(
- System.currentTimeMillis(), System.nanoTime());
- mUserRecordingLog = new ResearchLog(mUserRecordingFile, mLatinIME);
- mUserRecordingLogBuffer = new LogBuffer();
- resetRecordingTimer();
- }
-
- private boolean isMakingUserRecording() {
- return mUserRecordingLog != null;
- }
-
- private void resetRecordingTimer() {
- if (mUserRecordingTimeoutHandler == null) {
- mUserRecordingTimeoutHandler = new Handler();
- }
- clearRecordingTimer();
- mUserRecordingTimeoutHandler.postDelayed(mRecordingHandlerTimeoutRunnable,
- USER_RECORDING_TIMEOUT_MS);
- }
-
- private void clearRecordingTimer() {
- mUserRecordingTimeoutHandler.removeCallbacks(mRecordingHandlerTimeoutRunnable);
- }
-
- private Runnable mRecordingHandlerTimeoutRunnable = new Runnable() {
- @Override
- public void run() {
- cancelRecording();
- requestIndicatorRedraw();
- final Resources res = mLatinIME.getResources();
- Toast.makeText(mLatinIME, res.getString(R.string.research_feedback_recording_failure),
- Toast.LENGTH_LONG).show();
- }
- };
-
- private void cancelRecording() {
- if (mUserRecordingLog != null) {
- mUserRecordingLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS);
- }
- mUserRecordingLog = null;
- mUserRecordingLogBuffer = null;
- if (mFeedbackDialogBundle != null) {
- mFeedbackDialogBundle.putBoolean("HasRecording", false);
- }
- }
-
- private void saveRecording() {
- commitCurrentLogUnit();
- publishLogBuffer(mUserRecordingLogBuffer, mUserRecordingLog, true);
- mUserRecordingLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
- mUserRecordingLog = null;
- mUserRecordingLogBuffer = null;
-
- if (mFeedbackDialogBundle != null) {
- mFeedbackDialogBundle.putBoolean(FeedbackFragment.KEY_HAS_USER_RECORDING, true);
- }
- clearRecordingTimer();
- }
-
- // TODO: currently unreachable. Remove after being sure enable/disable is
- // not needed.
- /*
- public void enableOrDisable(final boolean showEnable, final LatinIME latinIME) {
- if (showEnable) {
- if (!sIsLogging) {
- setLoggingAllowed(true);
- }
- resumeLogging();
- Toast.makeText(latinIME,
- R.string.research_notify_session_logging_enabled,
- Toast.LENGTH_LONG).show();
- } else {
- Toast toast = Toast.makeText(latinIME,
- R.string.research_notify_session_log_deleting,
- Toast.LENGTH_LONG);
- toast.show();
- boolean isLogDeleted = abort();
- final long currentTime = System.currentTimeMillis();
- final long resumeTime = currentTime
- + TimeUnit.MINUTES.toMillis(SUSPEND_DURATION_IN_MINUTES);
- suspendLoggingUntil(resumeTime);
- toast.cancel();
- Toast.makeText(latinIME, R.string.research_notify_logging_suspended,
- Toast.LENGTH_LONG).show();
- }
- }
- */
-
- /**
- * Get the name of the first allowed account on the device.
- *
- * Allowed accounts must be in the domain given by ALLOWED_ACCOUNT_DOMAIN.
- *
- * @return The user's account name.
- */
- public String getAccountName() {
- if (sAccountType == null || sAccountType.isEmpty()) {
- return null;
- }
- if (sAllowedAccountDomain == null || sAllowedAccountDomain.isEmpty()) {
- return null;
- }
- final AccountManager manager = AccountManager.get(mLatinIME);
- // Filter first by account type.
- final Account[] accounts = manager.getAccountsByType(sAccountType);
-
- for (final Account account : accounts) {
- if (DEBUG) {
- Log.d(TAG, account.name);
- }
- final String[] parts = account.name.split("@");
- if (parts.length > 1 && parts[1].equals(sAllowedAccountDomain)) {
- return parts[0];
- }
- }
- return null;
- }
-
- private static final LogStatement LOGSTATEMENT_FEEDBACK =
- new LogStatement("UserFeedback", false, false, "contents", "accountName", "recording");
- public void sendFeedback(final String feedbackContents, final boolean includeHistory,
- final boolean isIncludingAccountName, final boolean isIncludingRecording) {
- String recording = "";
- if (isIncludingRecording) {
- // Try to read recording from recently written json file
- if (mUserRecordingFile != null) {
- FileChannel channel = null;
- try {
- channel = new FileInputStream(mUserRecordingFile).getChannel();
- final MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0,
- channel.size());
- // Android's openFileOutput() creates the file, so we use Android's default
- // Charset (UTF-8) here to read it.
- recording = Charset.defaultCharset().decode(buffer).toString();
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Could not find recording file", e);
- } catch (IOException e) {
- Log.e(TAG, "Error reading recording file", e);
- } finally {
- if (channel != null) {
- try {
- channel.close();
- } catch (IOException e) {
- Log.e(TAG, "Error closing recording file", e);
- }
- }
- }
- }
- }
- final LogUnit feedbackLogUnit = new LogUnit();
- final String accountName = isIncludingAccountName ? getAccountName() : "";
- feedbackLogUnit.addLogStatement(LOGSTATEMENT_FEEDBACK, SystemClock.uptimeMillis(),
- feedbackContents, accountName, recording);
-
- final ResearchLog feedbackLog = new FeedbackLog(mResearchLogDirectory.getLogFilePath(
- System.currentTimeMillis(), System.nanoTime()), mLatinIME);
- final LogBuffer feedbackLogBuffer = new LogBuffer();
- feedbackLogBuffer.shiftIn(feedbackLogUnit);
- publishLogBuffer(feedbackLogBuffer, feedbackLog, true /* isIncludingPrivateData */);
- feedbackLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
- uploadNow();
-
- if (isIncludingRecording && DEBUG_REPLAY_AFTER_FEEDBACK) {
- final Handler handler = new Handler();
- handler.postDelayed(new Runnable() {
- @Override
- public void run() {
- final ReplayData replayData =
- mMotionEventReader.readMotionEventData(mUserRecordingFile);
- mReplayer.replay(replayData, null);
- }
- }, TimeUnit.SECONDS.toMillis(1));
- }
-
- if (FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD) {
- // Use feedback string as a channel name to label feedback strings. Here we record the
- // string for prepopulating the field next time.
- final String channelName = feedbackContents;
- if (mPrefs == null) {
- return;
- }
- mPrefs.edit().putString(PREF_RESEARCH_SAVED_CHANNEL, channelName).apply();
- }
- }
-
- public void uploadNow() {
- if (DEBUG) {
- Log.d(TAG, "calling uploadNow()");
- }
- mLatinIME.startService(mUploadNowIntent);
- }
-
- public void onLeavingSendFeedbackDialog() {
- mInFeedbackDialog = false;
- }
-
- private void cancelFeedbackDialog() {
- if (isMakingUserRecording()) {
- cancelRecording();
- }
- mInFeedbackDialog = false;
- }
-
- public void initDictionary(final DictionaryFacilitator dictionaryFacilitator) {
- mDictionaryFacilitator = dictionaryFacilitator;
- // MainLogBuffer now has an out-of-date Suggest object. Close down MainLogBuffer and create
- // a new one.
- if (mMainLogBuffer != null) {
- restart();
- }
- }
-
- private void setIsPasswordView(boolean isPasswordView) {
- mIsPasswordView = isPasswordView;
- }
-
- /**
- * Returns true if logging is permitted.
- *
- * This method is called when adding a LogStatement to a LogUnit, and when adding a LogUnit to a
- * ResearchLog. It is checked in both places in case conditions change between these times, and
- * as a defensive measure in case refactoring changes the logging pipeline.
- */
- private boolean isAllowedToLogTo(final ResearchLog researchLog) {
- // Logging is never allowed in these circumstances
- if (mIsPasswordView) return false;
- if (!sIsLogging) return false;
- if (mInFeedbackDialog) {
- // The FeedbackDialog is up. Normal logging should not happen (the user might be trying
- // out things while the dialog is up, and their reporting of an issue may not be
- // representative of what they normally type). However, after the user has finished
- // entering their feedback, the logger packs their comments and an encoded version of
- // any demonstration of the issue into a special "FeedbackLog". So if the FeedbackLog
- // is the destination, we do want to allow logging to it.
- return researchLog.isFeedbackLog();
- }
- // No other exclusions. Logging is permitted.
- return true;
- }
-
- public void requestIndicatorRedraw() {
- if (!IS_SHOWING_INDICATOR) {
- return;
- }
- if (mMainKeyboardView == null) {
- return;
- }
- mMainKeyboardView.invalidateAllKeys();
- }
-
- private boolean isReplaying() {
- return mReplayer.isReplaying();
- }
-
- private int getIndicatorColor() {
- if (isMakingUserRecording()) {
- return Color.YELLOW;
- }
- if (isReplaying()) {
- return Color.GREEN;
- }
- return Color.RED;
- }
-
- public void paintIndicator(KeyboardView view, Paint paint, Canvas canvas, int width,
- int height) {
- // TODO: Reimplement using a keyboard background image specific to the ResearchLogger
- // and remove this method.
- // The check for MainKeyboardView ensures that the indicator only decorates the main
- // keyboard, not every keyboard.
- if (IS_SHOWING_INDICATOR && (isAllowedToLogTo(mMainResearchLog) || isReplaying())
- && view instanceof MainKeyboardView) {
- final int savedColor = paint.getColor();
- paint.setColor(getIndicatorColor());
- final Style savedStyle = paint.getStyle();
- paint.setStyle(Style.STROKE);
- final float savedStrokeWidth = paint.getStrokeWidth();
- if (IS_SHOWING_INDICATOR_CLEARLY) {
- paint.setStrokeWidth(5);
- canvas.drawLine(0, 0, 0, height, paint);
- canvas.drawLine(width, 0, width, height, paint);
- } else {
- // Put a tiny dot on the screen so a knowledgeable user can check whether it is
- // enabled. The dot is actually a zero-width, zero-height rectangle, placed at the
- // lower-right corner of the canvas, painted with a non-zero border width.
- paint.setStrokeWidth(3);
- canvas.drawRect(width - 1, height - 1, width, height, paint);
- }
- paint.setColor(savedColor);
- paint.setStyle(savedStyle);
- paint.setStrokeWidth(savedStrokeWidth);
- }
- }
-
- /**
- * Buffer a research log event, flagging it as privacy-sensitive.
- */
- private synchronized void enqueueEvent(final LogStatement logStatement,
- final Object... values) {
- enqueueEvent(mCurrentLogUnit, logStatement, values);
- }
-
- private synchronized void enqueueEvent(final LogUnit logUnit, final LogStatement logStatement,
- final Object... values) {
- assert values.length == logStatement.getKeys().length;
- if (isAllowedToLogTo(mMainResearchLog) && logUnit != null) {
- final long time = SystemClock.uptimeMillis();
- logUnit.addLogStatement(logStatement, time, values);
- }
- }
-
- private void setCurrentLogUnitContainsDigitFlag() {
- mCurrentLogUnit.setMayContainDigit();
- }
-
- private void setCurrentLogUnitContainsUserDeletions() {
- mCurrentLogUnit.setContainsUserDeletions();
- }
-
- private void setCurrentLogUnitCorrectionType(final int correctionType) {
- mCurrentLogUnit.setCorrectionType(correctionType);
- }
-
- /* package for test */ void commitCurrentLogUnit() {
- if (DEBUG) {
- Log.d(TAG, "commitCurrentLogUnit" + (mCurrentLogUnit.hasOneOrMoreWords() ?
- ": " + mCurrentLogUnit.getWordsAsString() : ""));
- }
- if (!mCurrentLogUnit.isEmpty()) {
- mMainLogBuffer.shiftIn(mCurrentLogUnit);
- if (mUserRecordingLogBuffer != null) {
- mUserRecordingLogBuffer.shiftIn(mCurrentLogUnit);
- }
- mCurrentLogUnit = new LogUnit();
- } else {
- if (DEBUG) {
- Log.d(TAG, "Warning: tried to commit empty log unit.");
- }
- }
- }
-
- private static final LogStatement LOGSTATEMENT_UNCOMMIT_CURRENT_LOGUNIT =
- new LogStatement("UncommitCurrentLogUnit", false, false);
- public void uncommitCurrentLogUnit(final String expectedWord,
- final boolean dumpCurrentLogUnit) {
- // The user has deleted this word and returned to the previous. Check that the word in the
- // logUnit matches the expected word. If so, restore the last log unit committed to be the
- // current logUnit. I.e., pull out the last LogUnit from all the LogBuffers, and make
- // it the mCurrentLogUnit so the new edits are captured with the word. Optionally dump the
- // contents of mCurrentLogUnit (useful if they contain deletions of the next word that
- // should not be reported to protect user privacy)
- //
- // Note that we don't use mLastLogUnit here, because it only goes one word back and is only
- // needed for reverts, which only happen one back.
- final LogUnit oldLogUnit = mMainLogBuffer.peekLastLogUnit();
-
- // Check that expected word matches. It's ok if both strings are null, because this is the
- // case where the LogUnit is storing a non-word, e.g. a separator.
- if (oldLogUnit != null) {
- // Because the word is stored in the LogUnit with digits scrubbed, the comparison must
- // be made on a scrubbed version of the expectedWord as well.
- final String scrubbedExpectedWord = scrubDigitsFromString(expectedWord);
- final String oldLogUnitWords = oldLogUnit.getWordsAsString();
- if (!TextUtils.equals(scrubbedExpectedWord, oldLogUnitWords)) return;
- }
-
- // Uncommit, merging if necessary.
- mMainLogBuffer.unshiftIn();
- if (oldLogUnit != null && !dumpCurrentLogUnit) {
- oldLogUnit.append(mCurrentLogUnit);
- mSavedDownEventTime = Long.MAX_VALUE;
- }
- if (oldLogUnit == null) {
- mCurrentLogUnit = new LogUnit();
- } else {
- mCurrentLogUnit = oldLogUnit;
- }
- enqueueEvent(LOGSTATEMENT_UNCOMMIT_CURRENT_LOGUNIT);
- if (DEBUG) {
- Log.d(TAG, "uncommitCurrentLogUnit (dump=" + dumpCurrentLogUnit + ") back to "
- + (mCurrentLogUnit.hasOneOrMoreWords() ? ": '"
- + mCurrentLogUnit.getWordsAsString() + "'" : ""));
- }
- }
-
- /**
- * Publish all the logUnits in the logBuffer, without doing any privacy filtering.
- */
- /* package for test */ void publishLogBuffer(final LogBuffer logBuffer,
- final ResearchLog researchLog, final boolean canIncludePrivateData) {
- publishLogUnits(logBuffer.getLogUnits(), researchLog, canIncludePrivateData);
- }
-
- 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);
- /**
- * Publish all LogUnits in a list.
- *
- * Any privacy checks should be performed before calling this method.
- */
- /* package for test */ void publishLogUnits(final List<LogUnit> logUnits,
- final ResearchLog researchLog, final boolean canIncludePrivateData) {
- final LogUnit openingLogUnit = new LogUnit();
- if (logUnits.isEmpty()) return;
- if (!isAllowedToLogTo(researchLog)) return;
- // LogUnits not containing private data, such as contextual data for the log, do not require
- // logSegment boundary statements.
- if (canIncludePrivateData) {
- openingLogUnit.addLogStatement(LOGSTATEMENT_LOG_SEGMENT_OPENING,
- SystemClock.uptimeMillis(), canIncludePrivateData);
- researchLog.publish(openingLogUnit, true /* isIncludingPrivateData */);
- }
- for (LogUnit logUnit : logUnits) {
- if (DEBUG) {
- Log.d(TAG, "publishLogBuffer: " + (logUnit.hasOneOrMoreWords()
- ? logUnit.getWordsAsString() : "<wordless>")
- + ", correction?: " + logUnit.containsUserDeletions());
- }
- researchLog.publish(logUnit, canIncludePrivateData);
- }
- if (canIncludePrivateData) {
- final LogUnit closingLogUnit = new LogUnit();
- closingLogUnit.addLogStatement(LOGSTATEMENT_LOG_SEGMENT_CLOSING,
- SystemClock.uptimeMillis());
- researchLog.publish(closingLogUnit, true /* isIncludingPrivateData */);
- }
- }
-
- 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 true;
- }
- }
- return false;
- }
-
- /**
- * Commit the portion of mCurrentLogUnit before maxTime as a worded logUnit.
- *
- * After this operation completes, mCurrentLogUnit will hold any logStatements that happened
- * after maxTime.
- */
- /* package for test */ void commitCurrentLogUnitAsWord(final String word, final long maxTime,
- final boolean isBatchMode) {
- if (word == null) {
- return;
- }
- if (word.length() > 0 && hasLetters(word)) {
- mCurrentLogUnit.setWords(word);
- }
- final LogUnit newLogUnit = mCurrentLogUnit.splitByTime(maxTime);
- enqueueCommitText(word, isBatchMode);
- commitCurrentLogUnit();
- mCurrentLogUnit = newLogUnit;
- }
-
- /**
- * Record the time of a MotionEvent.ACTION_DOWN.
- *
- * Warning: Not thread safe. Only call from the main thread.
- */
- private void setSavedDownEventTime(final long time) {
- mSavedDownEventTime = time;
- }
-
- public void onWordFinished(final String word, final boolean isBatchMode) {
- commitCurrentLogUnitAsWord(word, mSavedDownEventTime, isBatchMode);
- mSavedDownEventTime = Long.MAX_VALUE;
- }
-
- private static int scrubDigitFromCodePoint(int codePoint) {
- return Character.isDigit(codePoint) ? DIGIT_REPLACEMENT_CODEPOINT : codePoint;
- }
-
- /* package for test */ static String scrubDigitsFromString(final String s) {
- if (s == null) return null;
- StringBuilder sb = null;
- final int length = s.length();
- for (int i = 0; i < length; i = s.offsetByCodePoints(i, 1)) {
- final int codePoint = Character.codePointAt(s, i);
- if (Character.isDigit(codePoint)) {
- if (sb == null) {
- sb = new StringBuilder(length);
- sb.append(s.substring(0, i));
- }
- sb.appendCodePoint(DIGIT_REPLACEMENT_CODEPOINT);
- } else {
- if (sb != null) {
- sb.appendCodePoint(codePoint);
- }
- }
- }
- if (sb == null) {
- return s;
- } else {
- return sb.toString();
- }
- }
-
- private String scrubWord(String word) {
- if (mDictionaryFacilitator != null && mDictionaryFacilitator.isValidMainDictWord(word)) {
- return word;
- }
- return WORD_REPLACEMENT_STRING;
- }
-
- // 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",
- "isDevTeamBuild");
- public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
- final SharedPreferences prefs) {
- final ResearchLogger researchLogger = getInstance();
- if (editorInfo != null) {
- 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 String uuid = ResearchSettings.readResearchLoggerUuid(researchLogger.mPrefs);
- researchLogger.enqueueEvent(LOGSTATEMENT_LATIN_IME_ON_START_INPUT_VIEW_INTERNAL,
- uuid, editorInfo.packageName, Integer.toHexString(editorInfo.inputType),
- Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId,
- Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName,
- OUTPUT_FORMAT_VERSION, IS_LOGGING_EVERYTHING,
- researchLogger.isDevTeamBuild());
- // Commit the logUnit so the LatinImeOnStartInputViewInternal event is in its own
- // logUnit at the beginning of the log.
- researchLogger.commitCurrentLogUnit();
- } catch (final NameNotFoundException e) {
- Log.e(TAG, "NameNotFound", e);
- }
- }
- }
-
- // TODO: Update this heuristic pattern to something more reliable. Developer builds tend to
- // have the developer name and year embedded.
- private static final Pattern developerBuildRegex = Pattern.compile("[A-Za-z]\\.20[1-9]");
- private boolean isDevTeamBuild() {
- try {
- final PackageInfo packageInfo;
- packageInfo = mLatinIME.getPackageManager().getPackageInfo(mLatinIME.getPackageName(),
- 0);
- final String versionName = packageInfo.versionName;
- return developerBuildRegex.matcher(versionName).find();
- } catch (final NameNotFoundException e) {
- Log.e(TAG, "Could not determine package name", e);
- return false;
- }
- }
-
- /**
- * 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();
- researchLogger.enqueueEvent(LOGSTATEMENT_PREFS_CHANGED, prefs);
- }
-
- /**
- * 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("MotionEvent", true, false, "action",
- LogStatement.KEY_IS_LOGGING_RELATED, "motionEvent");
- public static void mainKeyboardView_processMotionEvent(final MotionEvent me) {
- if (me == null) {
- return;
- }
- final int action = me.getActionMasked();
- final long eventTime = me.getEventTime();
- final String actionString = LoggingUtils.getMotionEventActionTypeString(action);
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT,
- actionString, false /* IS_LOGGING_RELATED */, MotionEvent.obtain(me));
- if (action == MotionEvent.ACTION_DOWN) {
- // Subtract 1 from eventTime so the down event is included in the later
- // LogUnit, not the earlier (the test is for inequality).
- researchLogger.setSavedDownEventTime(eventTime - 1);
- }
- // Refresh the timer in case we are capturing user feedback.
- if (researchLogger.isMakingUserRecording()) {
- researchLogger.resetRecordingTimer();
- }
- }
-
- /**
- * 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();
- 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);
- }
- /**
- * 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) {
- // 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 });
- }
-
- /**
- * The IME is finishing; it is either being destroyed, or is about to be hidden.
- *
- * 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_ONFINISHINPUTVIEWINTERNAL =
- new LogStatement("LatinIMEOnFinishInputViewInternal", false, false, "isTextTruncated",
- "text");
- public static void latinIME_onFinishInputViewInternal(final boolean finishingInput) {
- // The finishingInput flag is set in InputMethodService. It is true if called from
- // doFinishInput(), which can be called as part of doStartInput(). This can happen at times
- // when the IME is not closing, such as when powering up. The finishinInput flag is false
- // if called from finishViews(), which is called from hideWindow() and onDestroy(). These
- // are the situations in which we want to finish up the researchLog.
- if (!finishingInput) {
- final ResearchLogger researchLogger = getInstance();
- // 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_ONFINISHINPUTVIEWINTERNAL,
- true /* isTextTruncated */, "" /* text */);
- researchLogger.commitCurrentLogUnit();
- getInstance().stop();
- }
- }
-
- /**
- * 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,
- final int composingSpanEnd, final RichInputConnection connection) {
- String word = "";
- if (connection != null) {
- TextRange range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
- if (range != null) {
- word = range.mWord.toString();
- }
- }
- final ResearchLogger researchLogger = getInstance();
- final String scrubbedWord = researchLogger.scrubWord(word);
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONUPDATESELECTION, lastSelectionStart,
- lastSelectionEnd, oldSelStart, oldSelEnd, newSelStart, newSelEnd,
- composingSpanStart, composingSpanEnd, false /* expectingUpdateSelection */,
- false /* expectingUpdateSelectionFromLogger */, scrubbedWord);
- }
-
- /**
- * Log a call to LatinIME.onTextInput().
- *
- * SystemResponse: Raw text is added to the TextView.
- */
- public static void latinIME_onTextInput(final String text, final boolean isBatchMode) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.commitCurrentLogUnitAsWord(text, Long.MAX_VALUE, isBatchMode);
- }
-
- /**
- * Log a revert of onTextInput() (known in the IME as "EnteredText").
- *
- * SystemResponse: Remove the LogUnit recording the textInput
- */
- public static void latinIME_handleBackspace_cancelTextInput(final String text) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.uncommitCurrentLogUnit(text, true /* dumpCurrentLogUnit */);
- }
-
- /**
- * 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", "isBatchMode", "score", "kind", "sourceDict");
- /**
- * Log a call to LatinIME.pickSuggestionManually().
- *
- * @param replacedWord the typed word that this manual suggestion replaces. May not be null.
- * @param index the index in the suggestion strip
- * @param suggestion the committed suggestion. May not be null.
- * @param isBatchMode whether this was input in batch mode, aka gesture.
- * @param score the internal score of the suggestion, as output by the dictionary
- * @param kind the kind of suggestion, as one of the SuggestedWordInfo#KIND_* constants
- * @param sourceDict the source origin of this word, as one of the Dictionary#TYPE_* constants.
- */
- public static void latinIME_pickSuggestionManually(final String replacedWord,
- final int index, final String suggestion, final boolean isBatchMode,
- final int score, final int kind, final String sourceDict) {
- final ResearchLogger researchLogger = getInstance();
- // Note : suggestion can't be null here, because it's only called in a place where it
- // can't be null.
- if (!replacedWord.equals(suggestion.toString())) {
- // The user chose something other than what was already there.
- researchLogger.setCurrentLogUnitContainsUserDeletions();
- researchLogger.setCurrentLogUnitCorrectionType(LogUnit.CORRECTIONTYPE_TYPO);
- }
- final String scrubbedWord = scrubDigitsFromString(suggestion);
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY,
- scrubDigitsFromString(replacedWord), index,
- scrubbedWord, Constants.SUGGESTION_STRIP_COORDINATE,
- Constants.SUGGESTION_STRIP_COORDINATE, isBatchMode, score, kind, sourceDict);
- researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, Long.MAX_VALUE, isBatchMode);
- researchLogger.mStatistics.recordManualSuggestion(SystemClock.uptimeMillis());
- }
-
- /**
- * 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", "isPrediction");
- public static void latinIME_punctuationSuggestion(final int index, final String suggestion,
- final boolean isBatchMode, final boolean isPrediction) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PUNCTUATIONSUGGESTION, index, suggestion,
- Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
- isPrediction);
- researchLogger.commitCurrentLogUnitAsWord(suggestion, Long.MAX_VALUE, isBatchMode);
- }
-
- /**
- * Log a call to LatinIME.sendKeyCodePoint().
- *
- * SystemResponse: The IME is inserting text into the TextView for non-word-constituent,
- * strings (separators, numbers, other symbols).
- */
- private static final LogStatement LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT =
- new LogStatement("LatinIMESendKeyCodePoint", true, false, "code");
- public static void latinIME_sendKeyCodePoint(final int code) {
- final ResearchLogger researchLogger = getInstance();
- final LogUnit phantomSpaceLogUnit = researchLogger.mPhantomSpaceLogUnit;
- if (phantomSpaceLogUnit == null) {
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT,
- Constants.printableCode(scrubDigitFromCodePoint(code)));
- if (Character.isDigit(code)) {
- researchLogger.setCurrentLogUnitContainsDigitFlag();
- }
- researchLogger.commitCurrentLogUnit();
- } else {
- researchLogger.enqueueEvent(phantomSpaceLogUnit, LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT,
- Constants.printableCode(scrubDigitFromCodePoint(code)));
- if (Character.isDigit(code)) {
- phantomSpaceLogUnit.setMayContainDigit();
- }
- researchLogger.mMainLogBuffer.shiftIn(phantomSpaceLogUnit);
- if (researchLogger.mUserRecordingLogBuffer != null) {
- researchLogger.mUserRecordingLogBuffer.shiftIn(phantomSpaceLogUnit);
- }
- researchLogger.mPhantomSpaceLogUnit = null;
- }
- }
-
- /**
- * Log a call to LatinIME.promotePhantomSpace().
- *
- * SystemResponse: The IME is inserting a real space in place of a phantom space.
- */
- private static final LogStatement LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE =
- new LogStatement("LatinIMEPromotePhantomSpace", false, false);
- public static void latinIME_promotePhantomSpace() {
- // A phantom space is always added before the text that triggered it. The triggering text
- // and the events that created it will be in mCurrentLogUnit, but the phantom space should
- // be in its own LogUnit, committed before the triggering text. Although it is created
- // here, it is not added to the LogBuffer until the following call to
- // latinIME_sendKeyCodePoint, because SENDKEYCODEPOINT LogStatement also must go into that
- // LogUnit.
- final ResearchLogger researchLogger = getInstance();
- researchLogger.mPhantomSpaceLogUnit = new LogUnit();
- researchLogger.enqueueEvent(researchLogger.mPhantomSpaceLogUnit,
- LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE);
- }
-
- /**
- * 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, "originalCharacters",
- "charactersAfterSwap");
- public static void latinIME_swapSwapperAndSpace(final CharSequence originalCharacters,
- final String charactersAfterSwap) {
- final ResearchLogger researchLogger = getInstance();
- final LogUnit logUnit;
- logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
- if (logUnit != null) {
- researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_SWAPSWAPPERANDSPACE,
- originalCharacters, charactersAfterSwap);
- }
- }
-
- /**
- * Log a call to LatinIME.maybeDoubleSpacePeriod().
- *
- * SystemResponse: Two spaces have been replaced by period space.
- */
- public static void latinIME_maybeDoubleSpacePeriod(final String text,
- final boolean isBatchMode) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.commitCurrentLogUnitAsWord(text, Long.MAX_VALUE, isBatchMode);
- }
-
- /**
- * 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(LOGSTATEMENT_MAINKEYBOARDVIEW_ONLONGPRESS);
- }
-
- /**
- * 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",
- "supportsSwitchingToShortcutIme", "hasShortcutKey", "languageSwitchKeyEnabled",
- "isMultiLine", "tw", "th",
- "keys");
- public static void mainKeyboardView_setKeyboard(final Keyboard keyboard,
- final int orientation) {
- final KeyboardId kid = keyboard.mId;
- final boolean isPasswordView = kid.passwordInput();
- final ResearchLogger researchLogger = getInstance();
- researchLogger.setIsPasswordView(isPasswordView);
- researchLogger.enqueueEvent(LOGSTATEMENT_MAINKEYBOARDVIEW_SETKEYBOARD,
- KeyboardId.elementIdToName(kid.mElementId),
- kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
- orientation, kid.mWidth, KeyboardId.modeName(kid.mMode), kid.imeAction(),
- kid.navigateNext(), kid.navigatePrevious(), kid.mClobberSettingsKey,
- isPasswordView, kid.mSupportsSwitchingToShortcutIme, kid.mHasShortcutKey,
- kid.mLanguageSwitchKeyEnabled, kid.isMultiLine(), keyboard.mOccupiedWidth,
- keyboard.mOccupiedHeight, keyboard.getSortedKeys());
- }
-
- /**
- * 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", "separatorString");
- public static void latinIME_revertCommit(final String committedWord,
- final String originallyTypedWord, final boolean isBatchMode,
- final String separatorString) {
- // TODO: Prioritize adding a unit test for this method (as it is especially complex)
- // TODO: Update the UserRecording LogBuffer as well as the MainLogBuffer
- final ResearchLogger researchLogger = getInstance();
- //
- // 1. Remove separator LogUnit
- final LogUnit lastLogUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
- // Check that we're not at the beginning of input
- if (lastLogUnit == null) return;
- // Check that we're after a separator
- if (lastLogUnit.getWordsAsString() != null) return;
- // Remove separator
- final LogUnit separatorLogUnit = researchLogger.mMainLogBuffer.unshiftIn();
-
- // 2. Add revert LogStatement
- final LogUnit revertedLogUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
- if (revertedLogUnit == null) return;
- if (!revertedLogUnit.getWordsAsString().equals(scrubDigitsFromString(committedWord))) {
- // Any word associated with the reverted LogUnit has already had its digits scrubbed, so
- // any digits in the committedWord argument must also be scrubbed for an accurate
- // comparison.
- return;
- }
- researchLogger.enqueueEvent(revertedLogUnit, LOGSTATEMENT_LATINIME_REVERTCOMMIT,
- committedWord, originallyTypedWord, separatorString);
-
- // 3. Update the word associated with the LogUnit
- revertedLogUnit.setWords(originallyTypedWord);
- revertedLogUnit.setContainsUserDeletions();
-
- // 4. Re-add the separator LogUnit
- researchLogger.mMainLogBuffer.shiftIn(separatorLogUnit);
-
- // 5. Record stats
- researchLogger.mStatistics.recordRevertCommit(SystemClock.uptimeMillis());
- }
-
- /**
- * 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(LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCANCELINPUT);
- }
-
- /**
- * 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 ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCODEINPUT,
- Constants.printableCode(scrubDigitFromCodePoint(code)),
- outputText == null ? null : scrubDigitsFromString(outputText.toString()),
- x, y, ignoreModifierKey, altersCode, key.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) {
- getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONRELEASE,
- Constants.printableCode(scrubDigitFromCodePoint(primaryCode)), withSliding,
- ignoreModifierKey, key.isEnabled());
- }
- }
-
- /**
- * 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) {
- getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_ONDOWNEVENT, deltaT,
- distanceSquared);
- }
-
- /**
- * 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) {
- getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_ONMOVEEVENT, x, y, lastX, lastY);
- }
-
- /**
- * 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 ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_COMMITCOMPLETION,
- completionInfo);
- }
-
- /**
- * Log a call to RichInputConnection.revertDoubleSpacePeriod().
- *
- * SystemResponse: The IME has reverted ". ", which had previously replaced two typed spaces.
- */
- private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_REVERTDOUBLESPACEPERIOD =
- new LogStatement("RichInputConnectionRevertDoubleSpacePeriod", false, false);
- public static void richInputConnection_revertDoubleSpacePeriod() {
- final ResearchLogger researchLogger = getInstance();
- // An extra LogUnit is added for the period; this is removed here because of the revert.
- researchLogger.uncommitCurrentLogUnit(null, true /* dumpCurrentLogUnit */);
- // TODO: This will probably be lost as the user backspaces further. Figure out how to put
- // it into the right logUnit.
- researchLogger.enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_REVERTDOUBLESPACEPERIOD);
- }
-
- /**
- * Log a call to RichInputConnection.revertSwapPunctuation().
- *
- * SystemResponse: The IME has reverted a punctuation swap.
- */
- private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_REVERTSWAPPUNCTUATION =
- new LogStatement("RichInputConnectionRevertSwapPunctuation", false, false);
- public static void richInputConnection_revertSwapPunctuation() {
- getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_REVERTSWAPPUNCTUATION);
- }
-
- /**
- * 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 (or words) that the user more likely desired to type.
- */
- private static final LogStatement LOGSTATEMENT_LATINIME_COMMITCURRENTAUTOCORRECTION =
- new LogStatement("LatinIMECommitCurrentAutoCorrection", true, true, "typedWord",
- "autoCorrection", "separatorString");
- public static void latinIme_commitCurrentAutoCorrection(final String typedWord,
- final String autoCorrection, final String separatorString, final boolean isBatchMode,
- final SuggestedWords suggestedWords) {
- final String scrubbedTypedWord = scrubDigitsFromString(typedWord);
- final String scrubbedAutoCorrection = scrubDigitsFromString(autoCorrection);
- final ResearchLogger researchLogger = getInstance();
- researchLogger.mCurrentLogUnit.initializeSuggestions(suggestedWords);
- researchLogger.onWordFinished(scrubbedAutoCorrection, isBatchMode);
-
- // Add the autocorrection logStatement at the end of the logUnit for the committed word.
- // We have to do this after calling commitCurrentLogUnitAsWord, because it may split the
- // current logUnit, and then we have to peek to get the logUnit reference back.
- final LogUnit logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
- // TODO: Add test to confirm that the commitCurrentAutoCorrection log statement should
- // always be added to logUnit (if non-null) and not mCurrentLogUnit.
- researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_COMMITCURRENTAUTOCORRECTION,
- scrubbedTypedWord, scrubbedAutoCorrection, separatorString);
- }
-
- private boolean isExpectingCommitText = false;
-
- /**
- * 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 String committedWord,
- final int newCursorPosition, final boolean isBatchMode) {
- final ResearchLogger researchLogger = getInstance();
- // Only include opening and closing logSegments if private data is included
- final String scrubbedWord = scrubDigitsFromString(committedWord);
- if (!researchLogger.isExpectingCommitText) {
- researchLogger.enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTIONCOMMITTEXT,
- newCursorPosition);
- researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, Long.MAX_VALUE, isBatchMode);
- }
- researchLogger.isExpectingCommitText = false;
- }
-
- /**
- * Shared events for logging committed text.
- *
- * The "CommitTextEventHappened" LogStatement is written to the log even if privacy rules
- * indicate that the word contents should not be logged. It has no contents, and only serves to
- * record the event and thereby make it easier to calculate word-level statistics even when the
- * word contents are unknown.
- */
- private static final LogStatement LOGSTATEMENT_COMMITTEXT =
- new LogStatement("CommitText", true /* isPotentiallyPrivate */,
- false /* isPotentiallyRevealing */, "committedText", "isBatchMode");
- private static final LogStatement LOGSTATEMENT_COMMITTEXT_EVENT_HAPPENED =
- new LogStatement("CommitTextEventHappened", false /* isPotentiallyPrivate */,
- false /* isPotentiallyRevealing */);
- private void enqueueCommitText(final String word, final boolean isBatchMode) {
- // Event containing the word; will be published only if privacy checks pass
- enqueueEvent(LOGSTATEMENT_COMMITTEXT, word, isBatchMode);
- // Event not containing the word; will always be published
- enqueueEvent(LOGSTATEMENT_COMMITTEXT_EVENT_HAPPENED);
- }
-
- /**
- * 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) {
- getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT,
- beforeLength, afterLength);
- }
-
- /**
- * 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(LOGSTATEMENT_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT);
- }
-
- /**
- * 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);
- }
-
- /**
- * 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) {
- getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_SENDKEYEVENT,
- keyEvent.getEventTime(), keyEvent.getAction(), keyEvent.getKeyCode());
- }
-
- /**
- * 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");
- }
- getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_SETCOMPOSINGTEXT, text,
- newCursorPosition);
- }
-
- /**
- * 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) {
- getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_SETSELECTION, from, to);
- }
-
- /**
- * 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) {
- getInstance().enqueueEvent(LOGSTATEMENT_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT,
- MotionEvent.obtain(me));
- }
- }
-
- /**
- * 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) {
- getInstance().enqueueEvent(LOGSTATEMENT_SUGGESTIONSTRIPVIEW_SETSUGGESTIONS,
- suggestedWords);
- }
- }
-
- /**
- * 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(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", "suggestedWords");
- public static void latinIME_onEndBatchInput(final CharSequence enteredText,
- final int enteredWordPos, final SuggestedWords suggestedWords) {
- final ResearchLogger researchLogger = getInstance();
- if (!TextUtils.isEmpty(enteredText) && hasLetters(enteredText.toString())) {
- researchLogger.mCurrentLogUnit.setWords(enteredText.toString());
- }
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONENDBATCHINPUT, enteredText,
- enteredWordPos, suggestedWords);
- researchLogger.mCurrentLogUnit.initializeSuggestions(suggestedWords);
- researchLogger.mStatistics.recordGestureInput(enteredText.length(),
- SystemClock.uptimeMillis());
- }
-
- private static final LogStatement LOGSTATEMENT_LATINIME_HANDLEBACKSPACE =
- new LogStatement("LatinIMEHandleBackspace", true, false, "numCharacters");
- /**
- * Log a call to LatinIME.handleBackspace() that is not a batch delete.
- *
- * UserInput: The user is deleting one or more characters by hitting the backspace key once.
- * The covers single character deletes as well as deleting selections.
- *
- * @param numCharacters how many characters the backspace operation deleted
- * @param shouldUncommitLogUnit whether to uncommit the last {@code LogUnit} in the
- * {@code LogBuffer}
- */
- public static void latinIME_handleBackspace(final int numCharacters,
- final boolean shouldUncommitLogUnit) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLEBACKSPACE, numCharacters);
- if (shouldUncommitLogUnit) {
- ResearchLogger.getInstance().uncommitCurrentLogUnit(
- null, true /* dumpCurrentLogUnit */);
- }
- }
-
- /**
- * Log a call to LatinIME.handleBackspace() that is a batch delete.
- *
- * 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",
- "numCharacters");
- public static void latinIME_handleBackspace_batch(final CharSequence deletedText,
- final int numCharacters) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLEBACKSPACE_BATCH, deletedText,
- numCharacters);
- researchLogger.mStatistics.recordGestureDelete(deletedText.length(),
- SystemClock.uptimeMillis());
- researchLogger.uncommitCurrentLogUnit(deletedText.toString(),
- false /* dumpCurrentLogUnit */);
- }
-
- /**
- * Log a long interval between user operation.
- *
- * UserInput: The user has not done anything for a while.
- */
- private static final LogStatement LOGSTATEMENT_ONUSERPAUSE = new LogStatement("OnUserPause",
- false, false, "intervalInMs");
- public static void onUserPause(final long interval) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_ONUSERPAUSE, interval);
- }
-
- /**
- * Record the current time in case the LogUnit is later split.
- *
- * If the current logUnit is split, then tapping, motion events, etc. before this time should
- * be assigned to one LogUnit, and events after this time should go into the following LogUnit.
- */
- public static void recordTimeForLogUnitSplit() {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.setSavedDownEventTime(SystemClock.uptimeMillis());
- researchLogger.mSavedDownEventTime = Long.MAX_VALUE;
- }
-
- /**
- * Log a call to LatinIME.handleSeparator()
- *
- * SystemResponse: The system is inserting a separator character, possibly performing auto-
- * correction or other actions appropriate at the end of a word.
- */
- private static final LogStatement LOGSTATEMENT_LATINIME_HANDLESEPARATOR =
- new LogStatement("LatinIMEHandleSeparator", false, false, "primaryCode",
- "isComposingWord");
- public static void latinIME_handleSeparator(final int primaryCode,
- final boolean isComposingWord) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLESEPARATOR, primaryCode,
- isComposingWord);
- }
-
- /**
- * Call this method when the logging system has attempted publication of an n-gram.
- *
- * Statistics are gathered about the success or failure.
- *
- * @param publishabilityResultCode a result code as defined by
- * {@code MainLogBuffer.PUBLISHABILITY_*}
- */
- static void recordPublishabilityResultCode(final int publishabilityResultCode) {
- final ResearchLogger researchLogger = getInstance();
- final Statistics statistics = researchLogger.mStatistics;
- statistics.recordPublishabilityResultCode(publishabilityResultCode);
- }
-
- /**
- * 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", "correctedWordsCount", "autoCorrectionsCount",
- "publishableCount", "unpublishableStoppingCount",
- "unpublishableIncorrectWordCount", "unpublishableSampledTooRecentlyCount",
- "unpublishableDictionaryUnavailableCount", "unpublishableMayContainDigitCount",
- "unpublishableNotInDictionaryCount");
- private static void logStatistics() {
- final ResearchLogger researchLogger = getInstance();
- final Statistics statistics = researchLogger.mStatistics;
- 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, statistics.mCorrectedWordsCount,
- statistics.mAutoCorrectionsCount, statistics.mPublishableCount,
- statistics.mUnpublishableStoppingCount, statistics.mUnpublishableIncorrectWordCount,
- statistics.mUnpublishableSampledTooRecently,
- statistics.mUnpublishableDictionaryUnavailable,
- statistics.mUnpublishableMayContainDigit, statistics.mUnpublishableNotInDictionary);
- }
-}
diff --git a/java/src/com/android/inputmethod/research/ResearchSettings.java b/java/src/com/android/inputmethod/research/ResearchSettings.java
deleted file mode 100644
index c0bc03fde..000000000
--- a/java/src/com/android/inputmethod/research/ResearchSettings.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2013 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 java.util.UUID;
-
-public final class ResearchSettings {
- public static final String PREF_RESEARCH_LOGGER_UUID = "pref_research_logger_uuid";
- public static final String PREF_RESEARCH_LOGGER_ENABLED_FLAG =
- "pref_research_logger_enabled_flag";
- public static final String PREF_RESEARCH_LOGGER_HAS_SEEN_SPLASH =
- "pref_research_logger_has_seen_splash";
- public static final String PREF_RESEARCH_LAST_DIR_CLEANUP_TIME =
- "pref_research_last_dir_cleanup_time";
-
- private ResearchSettings() {
- // Intentional empty constructor for singleton.
- }
-
- public static String readResearchLoggerUuid(final SharedPreferences prefs) {
- if (prefs.contains(PREF_RESEARCH_LOGGER_UUID)) {
- return prefs.getString(PREF_RESEARCH_LOGGER_UUID, null);
- }
- // Generate a random string as uuid if not yet set
- final String newUuid = UUID.randomUUID().toString();
- prefs.edit().putString(PREF_RESEARCH_LOGGER_UUID, newUuid).apply();
- return newUuid;
- }
-
- public static boolean readResearchLoggerEnabledFlag(final SharedPreferences prefs) {
- return prefs.getBoolean(PREF_RESEARCH_LOGGER_ENABLED_FLAG, false);
- }
-
- public static void writeResearchLoggerEnabledFlag(final SharedPreferences prefs,
- final boolean isEnabled) {
- prefs.edit().putBoolean(PREF_RESEARCH_LOGGER_ENABLED_FLAG, isEnabled).apply();
- }
-
- public static boolean readHasSeenSplash(final SharedPreferences prefs) {
- return prefs.getBoolean(PREF_RESEARCH_LOGGER_HAS_SEEN_SPLASH, false);
- }
-
- public static void writeHasSeenSplash(final SharedPreferences prefs,
- final boolean hasSeenSplash) {
- prefs.edit().putBoolean(PREF_RESEARCH_LOGGER_HAS_SEEN_SPLASH, hasSeenSplash).apply();
- }
-
- public static long readResearchLastDirCleanupTime(final SharedPreferences prefs) {
- return prefs.getLong(PREF_RESEARCH_LAST_DIR_CLEANUP_TIME, 0L);
- }
-
- public static void writeResearchLastDirCleanupTime(final SharedPreferences prefs,
- final long lastDirCleanupTime) {
- prefs.edit().putLong(PREF_RESEARCH_LAST_DIR_CLEANUP_TIME, lastDirCleanupTime).apply();
- }
-}
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
deleted file mode 100644
index fd323a104..000000000
--- a/java/src/com/android/inputmethod/research/Statistics.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * 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.util.Log;
-
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.define.ProductionFlag;
-
-import java.util.concurrent.TimeUnit;
-
-public class Statistics {
- private static final String TAG = Statistics.class.getSimpleName();
- private static final boolean DEBUG = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
-
- // TODO: Cleanup comments to only including those giving meaningful information.
- // Number of characters entered during a typing session
- int mCharCount;
- // Number of letter characters entered during a typing session
- int mLetterCount;
- // Number of number characters entered
- int mNumberCount;
- // Number of space characters entered
- int mSpaceCount;
- // Number of delete operations entered (taps on the backspace key)
- 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 words entered during a session.
- int mCorrectedWordsCount;
- // 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 that autocorrection was invoked.
- int mAutoCorrectionsCount;
- // Number of times a commit was reverted in this session.
- int mRevertCommitsCount;
- // Whether the text field was empty upon editing
- boolean mIsEmptyUponStarting;
- boolean mIsEmptinessStateKnown;
-
- // Counts of how often an n-gram is collected or not, and the reasons for the decision.
- // Keep consistent with publishability result code list in MainLogBuffer
- int mPublishableCount;
- int mUnpublishableStoppingCount;
- int mUnpublishableIncorrectWordCount;
- int mUnpublishableSampledTooRecently;
- int mUnpublishableDictionaryUnavailable;
- int mUnpublishableMayContainDigit;
- int mUnpublishableNotInDictionary;
-
- // Timers to count average time to enter a key, first press a delete key,
- // between delete keys, and then to return typing after a delete key.
- final AverageTimeCounter mKeyCounter = new AverageTimeCounter();
- final AverageTimeCounter mBeforeDeleteKeyCounter = new AverageTimeCounter();
- final AverageTimeCounter mDuringRepeatedDeleteKeysCounter = new AverageTimeCounter();
- final AverageTimeCounter mAfterDeleteKeyCounter = new AverageTimeCounter();
-
- static class AverageTimeCounter {
- int mCount;
- int mTotalTime;
-
- public void reset() {
- mCount = 0;
- mTotalTime = 0;
- }
-
- public void add(long deltaTime) {
- mCount++;
- mTotalTime += deltaTime;
- }
-
- public int getAverageTime() {
- if (mCount == 0) {
- return 0;
- }
- return mTotalTime / mCount;
- }
- }
-
- // To account for the interruptions when the user's attention is directed elsewhere, times
- // longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic.
- public static final long MIN_TYPING_INTERMISSION = TimeUnit.SECONDS.toMillis(2);
- public static final long MIN_DELETION_INTERMISSION = TimeUnit.SECONDS.toMillis(10);
-
- // The last time that a tap was performed
- private long mLastTapTime;
- // The type of the last keypress (delete key or not)
- boolean mIsLastKeyDeleteKey;
-
- private static final Statistics sInstance = new Statistics();
-
- public static Statistics getInstance() {
- return sInstance;
- }
-
- private Statistics() {
- reset();
- }
-
- public void reset() {
- mCharCount = 0;
- mLetterCount = 0;
- mNumberCount = 0;
- mSpaceCount = 0;
- mDeleteKeyCount = 0;
- mWordCount = 0;
- mDictionaryWordCount = 0;
- mSplitWordsCount = 0;
- mCorrectedWordsCount = 0;
- mGesturesInputCount = 0;
- mGesturesDeletedCount = 0;
- mManualSuggestionsCount = 0;
- mRevertCommitsCount = 0;
- mAutoCorrectionsCount = 0;
- mIsEmptyUponStarting = true;
- mIsEmptinessStateKnown = false;
- mKeyCounter.reset();
- mBeforeDeleteKeyCounter.reset();
- mDuringRepeatedDeleteKeysCounter.reset();
- mAfterDeleteKeyCounter.reset();
- mGesturesCharsCount = 0;
- mGesturesDeletedCount = 0;
- mPublishableCount = 0;
- mUnpublishableStoppingCount = 0;
- mUnpublishableIncorrectWordCount = 0;
- mUnpublishableSampledTooRecently = 0;
- mUnpublishableDictionaryUnavailable = 0;
- mUnpublishableMayContainDigit = 0;
- mUnpublishableNotInDictionary = 0;
-
- mLastTapTime = 0;
- mIsLastKeyDeleteKey = false;
- }
-
- public void recordChar(int codePoint, long time) {
- if (DEBUG) {
- Log.d(TAG, "recordChar() called");
- }
- if (codePoint == Constants.CODE_DELETE) {
- mDeleteKeyCount++;
- recordUserAction(time, true /* isDeletion */);
- } else {
- mCharCount++;
- if (Character.isDigit(codePoint)) {
- mNumberCount++;
- }
- if (Character.isLetter(codePoint)) {
- mLetterCount++;
- }
- if (Character.isSpaceChar(codePoint)) {
- mSpaceCount++;
- }
- recordUserAction(time, false /* isDeletion */);
- }
- }
-
- public void recordWordEntered(final boolean isDictionaryWord,
- final boolean containsCorrection) {
- mWordCount++;
- if (isDictionaryWord) {
- mDictionaryWordCount++;
- }
- if (containsCorrection) {
- mCorrectedWordsCount++;
- }
- }
-
- public void recordSplitWords() {
- mSplitWordsCount++;
- }
-
- public void recordGestureInput(final int numCharsEntered, final long time) {
- mGesturesInputCount++;
- mGesturesCharsCount += numCharsEntered;
- recordUserAction(time, false /* isDeletion */);
- }
-
- public void setIsEmptyUponStarting(final boolean isEmpty) {
- mIsEmptyUponStarting = isEmpty;
- mIsEmptinessStateKnown = true;
- }
-
- public void recordGestureDelete(final int length, final long time) {
- mGesturesDeletedCount++;
- recordUserAction(time, true /* isDeletion */);
- }
-
- public void recordManualSuggestion(final long time) {
- mManualSuggestionsCount++;
- recordUserAction(time, false /* isDeletion */);
- }
-
- public void recordAutoCorrection(final long time) {
- mAutoCorrectionsCount++;
- recordUserAction(time, false /* isDeletion */);
- }
-
- public void recordRevertCommit(final long time) {
- mRevertCommitsCount++;
- recordUserAction(time, true /* isDeletion */);
- }
-
- private void recordUserAction(final long time, final boolean isDeletion) {
- final long delta = time - mLastTapTime;
- if (isDeletion) {
- if (delta < MIN_DELETION_INTERMISSION) {
- if (mIsLastKeyDeleteKey) {
- mDuringRepeatedDeleteKeysCounter.add(delta);
- } else {
- mBeforeDeleteKeyCounter.add(delta);
- }
- } else {
- ResearchLogger.onUserPause(delta);
- }
- } else {
- if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) {
- mAfterDeleteKeyCounter.add(delta);
- } else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) {
- mKeyCounter.add(delta);
- } else {
- ResearchLogger.onUserPause(delta);
- }
- }
- mIsLastKeyDeleteKey = isDeletion;
- mLastTapTime = time;
- }
-
- public void recordPublishabilityResultCode(final int publishabilityResultCode) {
- // Keep consistent with publishability result code list in MainLogBuffer
- switch (publishabilityResultCode) {
- case MainLogBuffer.PUBLISHABILITY_PUBLISHABLE:
- mPublishableCount++;
- break;
- case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_STOPPING:
- mUnpublishableStoppingCount++;
- break;
- case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT:
- mUnpublishableIncorrectWordCount++;
- break;
- case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY:
- mUnpublishableSampledTooRecently++;
- break;
- case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE:
- mUnpublishableDictionaryUnavailable++;
- break;
- case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT:
- mUnpublishableMayContainDigit++;
- break;
- case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY:
- mUnpublishableNotInDictionary++;
- break;
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/research/Uploader.java b/java/src/com/android/inputmethod/research/Uploader.java
deleted file mode 100644
index c7ea3e69d..000000000
--- a/java/src/com/android/inputmethod/research/Uploader.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2013 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.Manifest;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.BatteryManager;
-import android.text.TextUtils;
-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;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-/**
- * Manages the uploading of ResearchLog files.
- */
-public final class Uploader {
- private static final String TAG = Uploader.class.getSimpleName();
- private static final boolean DEBUG = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- // Set IS_INHIBITING_AUTO_UPLOAD to true for local testing
- private static final boolean IS_INHIBITING_UPLOAD = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- private static final int BUF_SIZE = 1024 * 8;
-
- private final Context mContext;
- private final ResearchLogDirectory mResearchLogDirectory;
- private final URL mUrl;
-
- public Uploader(final Context context) {
- mContext = context;
- mResearchLogDirectory = new ResearchLogDirectory(context);
-
- final String urlString = context.getString(R.string.research_logger_upload_url);
- if (TextUtils.isEmpty(urlString)) {
- mUrl = null;
- return;
- }
- URL url = null;
- try {
- url = new URL(urlString);
- } catch (final MalformedURLException e) {
- Log.e(TAG, "Bad URL for uploading", e);
- }
- mUrl = url;
- }
-
- public boolean isPossibleToUpload() {
- return hasUploadingPermission() && mUrl != null && !IS_INHIBITING_UPLOAD;
- }
-
- private boolean hasUploadingPermission() {
- final PackageManager packageManager = mContext.getPackageManager();
- return packageManager.checkPermission(Manifest.permission.INTERNET,
- mContext.getPackageName()) == PackageManager.PERMISSION_GRANTED;
- }
-
- public boolean isConvenientToUpload() {
- return isExternallyPowered() && hasWifiConnection();
- }
-
- private boolean isExternallyPowered() {
- final Intent intent = mContext.registerReceiver(null, new IntentFilter(
- Intent.ACTION_BATTERY_CHANGED));
- final int pluggedState = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
- return pluggedState == BatteryManager.BATTERY_PLUGGED_AC
- || pluggedState == BatteryManager.BATTERY_PLUGGED_USB;
- }
-
- private boolean hasWifiConnection() {
- final ConnectivityManager manager =
- (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
- final NetworkInfo wifiInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
- return wifiInfo.isConnected();
- }
-
- public void doUpload() {
- final File[] files = mResearchLogDirectory.getUploadableLogFiles();
- if (files == null) return;
- for (final File file : files) {
- uploadFile(file);
- }
- }
-
- private void uploadFile(final File file) {
- if (DEBUG) {
- Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
- }
- final int contentLength = (int) file.length();
- HttpURLConnection connection = null;
- InputStream fileInputStream = null;
- try {
- fileInputStream = new FileInputStream(file);
- connection = (HttpURLConnection) mUrl.openConnection();
- connection.setRequestMethod("PUT");
- connection.setDoOutput(true);
- connection.setFixedLengthStreamingMode(contentLength);
- final OutputStream outputStream = connection.getOutputStream();
- uploadContents(fileInputStream, outputStream);
- if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
- Log.d(TAG, "upload failed: " + connection.getResponseCode());
- final InputStream netInputStream = connection.getInputStream();
- final BufferedReader reader = new BufferedReader(new InputStreamReader(
- netInputStream));
- String line;
- while ((line = reader.readLine()) != null) {
- Log.d(TAG, "| " + reader.readLine());
- }
- reader.close();
- return;
- }
- file.delete();
- if (DEBUG) {
- Log.d(TAG, "upload successful");
- }
- } catch (final IOException e) {
- Log.e(TAG, "Exception uploading file", e);
- } finally {
- if (fileInputStream != null) {
- try {
- fileInputStream.close();
- } catch (final IOException e) {
- Log.e(TAG, "Exception closing uploaded file", e);
- }
- }
- if (connection != null) {
- connection.disconnect();
- }
- }
- }
-
- private static void uploadContents(final InputStream is, final OutputStream os)
- throws IOException {
- // TODO: Switch to NIO.
- final byte[] buf = new byte[BUF_SIZE];
- int numBytesRead;
- while ((numBytesRead = is.read(buf)) != -1) {
- os.write(buf, 0, numBytesRead);
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java
deleted file mode 100644
index fd3f2f60e..000000000
--- a/java/src/com/android/inputmethod/research/UploaderService.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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.app.AlarmManager;
-import android.app.IntentService;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.SystemClock;
-
-/**
- * Service to invoke the uploader.
- *
- * Can be regularly invoked, invoked on boot, etc.
- */
-public final class UploaderService extends IntentService {
- private static final String TAG = UploaderService.class.getSimpleName();
- public static final long RUN_INTERVAL = AlarmManager.INTERVAL_HOUR;
- public static final String EXTRA_UPLOAD_UNCONDITIONALLY = UploaderService.class.getName()
- + ".extra.UPLOAD_UNCONDITIONALLY";
-
- public UploaderService() {
- super("Research Uploader Service");
- }
-
- @Override
- protected void onHandleIntent(final Intent intent) {
- // We may reach this point either because the alarm fired, or because the system explicitly
- // requested that an Upload occur. In the latter case, we want to cancel the alarm in case
- // it's about to fire.
- cancelAndRescheduleUploadingService(this, false /* needsRescheduling */);
-
- final Uploader uploader = new Uploader(this);
- if (!uploader.isPossibleToUpload()) return;
- if (isUploadingUnconditionally(intent.getExtras()) || uploader.isConvenientToUpload()) {
- uploader.doUpload();
- }
- cancelAndRescheduleUploadingService(this, true /* needsRescheduling */);
- }
-
- private boolean isUploadingUnconditionally(final Bundle bundle) {
- if (bundle == null) return false;
- if (bundle.containsKey(EXTRA_UPLOAD_UNCONDITIONALLY)) {
- return bundle.getBoolean(EXTRA_UPLOAD_UNCONDITIONALLY);
- }
- return false;
- }
-
- /**
- * Arrange for the UploaderService to be run on a regular basis.
- *
- * Any existing scheduled invocation of UploaderService is removed and optionally rescheduled.
- * This may cause problems if this method is called so often that no scheduled invocation is
- * ever run. But if the delay is short enough that it will go off when the user is sleeping,
- * then there should be no starvation.
- *
- * @param context {@link Context} object
- * @param needsRescheduling whether to schedule a future intent to be delivered to this service
- */
- public static void cancelAndRescheduleUploadingService(final Context context,
- final boolean needsRescheduling) {
- final Intent intent = new Intent(context, UploaderService.class);
- final PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
- final AlarmManager alarmManager = (AlarmManager) context.getSystemService(
- Context.ALARM_SERVICE);
- alarmManager.cancel(pendingIntent);
- if (needsRescheduling) {
- alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()
- + UploaderService.RUN_INTERVAL, pendingIntent);
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/research/ui/SplashScreen.java b/java/src/com/android/inputmethod/research/ui/SplashScreen.java
deleted file mode 100644
index 78ed668d1..000000000
--- a/java/src/com/android/inputmethod/research/ui/SplashScreen.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2013 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.ui;
-
-import android.app.AlertDialog.Builder;
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.Intent;
-import android.inputmethodservice.InputMethodService;
-import android.net.Uri;
-import android.os.IBinder;
-import android.view.Window;
-import android.view.WindowManager.LayoutParams;
-
-import com.android.inputmethod.latin.R.string;
-
-/**
- * Show a dialog when the user first opens the keyboard.
- *
- * The splash screen is a modal dialog box presented when the user opens this keyboard for the first
- * time. It is useful for giving specific warnings that must be shown to the user before use.
- *
- * While the splash screen does share with the setup wizard the common goal of presenting
- * information to the user before use, they are presented at different times and with different
- * capabilities. The setup wizard is launched by tapping on the icon, and walks the user through
- * the setup process. It can, however, be bypassed by enabling the keyboard from Settings directly.
- * The splash screen cannot be bypassed, and is therefore more appropriate for obtaining user
- * consent.
- */
-public class SplashScreen {
- public interface UserConsentListener {
- public void onSplashScreenUserClickedOk();
- }
-
- final UserConsentListener mListener;
- final Dialog mSplashDialog;
-
- public SplashScreen(final InputMethodService inputMethodService,
- final UserConsentListener listener) {
- mListener = listener;
- final Builder builder = new Builder(inputMethodService)
- .setTitle(string.research_splash_title)
- .setMessage(string.research_splash_content)
- .setPositiveButton(android.R.string.yes,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mListener.onSplashScreenUserClickedOk();
- mSplashDialog.dismiss();
- }
- })
- .setNegativeButton(android.R.string.no,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- final String packageName = inputMethodService.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);
- inputMethodService.startActivity(intent);
- }
- })
- .setCancelable(true)
- .setOnCancelListener(
- new OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- inputMethodService.requestHideSelf(0);
- }
- });
- mSplashDialog = builder.create();
- }
-
- /**
- * Show the splash screen.
- *
- * The user must consent to the terms presented in the SplashScreen before they can use the
- * keyboard. If they cancel instead, they are given the option to uninstall the keybard.
- *
- * @param windowToken {@link IBinder} to attach dialog to
- */
- public void showSplashScreen(final IBinder windowToken) {
- final Window window = mSplashDialog.getWindow();
- final LayoutParams lp = window.getAttributes();
- lp.token = windowToken;
- lp.type = LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
- window.setAttributes(lp);
- window.addFlags(LayoutParams.FLAG_ALT_FOCUSABLE_IM);
- mSplashDialog.show();
- }
-
- public boolean isShowing() {
- return mSplashDialog.isShowing();
- }
-}