diff options
Diffstat (limited to 'java/src/com/android/inputmethod')
35 files changed, 4 insertions, 5523 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/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/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(); - } -} |