diff options
Diffstat (limited to 'java/src/com/android/inputmethod/research/ResearchLogger.java')
-rw-r--r-- | java/src/com/android/inputmethod/research/ResearchLogger.java | 229 |
1 files changed, 100 insertions, 129 deletions
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index 57b740b95..da9c61103 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -20,11 +20,7 @@ import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOAR import android.accounts.Account; import android.accounts.AccountManager; -import android.app.AlertDialog; -import android.app.Dialog; import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageInfo; @@ -34,7 +30,6 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; -import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -42,12 +37,9 @@ import android.os.IBinder; import android.os.SystemClock; import android.preference.PreferenceManager; import android.text.TextUtils; -import android.text.format.DateUtils; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; -import android.view.Window; -import android.view.WindowManager; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; @@ -61,15 +53,16 @@ import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.latin.InputTypeUtils; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputConnection; -import com.android.inputmethod.latin.RichInputConnection.Range; import com.android.inputmethod.latin.Suggest; 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.TextRange; import com.android.inputmethod.research.MotionEventReader.ReplayData; +import com.android.inputmethod.research.ui.SplashScreen; import java.io.File; import java.io.FileInputStream; @@ -81,6 +74,7 @@ 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 @@ -94,12 +88,12 @@ import java.util.regex.Pattern; * This functionality is off by default. See * {@link ProductionFlag#USES_DEVELOPMENT_ONLY_DIAGNOSTICS}. */ -public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener { +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 splash screen code into separate class. // 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. @@ -138,15 +132,18 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang // 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 String WHITESPACE_SEPARATORS = " \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 = 5 * 1000; - private static final long RESEARCHLOG_ABORT_TIMEOUT_IN_MS = 5 * 1000; - private static final long DURATION_BETWEEN_DIR_CLEANUP_IN_MS = DateUtils.DAY_IN_MILLIS; - private static final long MAX_LOGFILE_AGE_IN_MS = 4 * DateUtils.DAY_IN_MILLIS; + 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; @@ -184,8 +181,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private final MotionEventReader mMotionEventReader = new MotionEventReader(); private final Replayer mReplayer = Replayer.getInstance(); private ResearchLogDirectory mResearchLogDirectory; + private SplashScreen mSplashScreen; - private Intent mUploadIntent; private Intent mUploadNowIntent; /* package for test */ LogUnit mCurrentLogUnit = new LogUnit(); @@ -200,7 +197,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang // 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 = 30L * DateUtils.SECOND_IN_MILLIS; + 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 @@ -238,7 +235,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang resetLogBuffers(); // Initialize external services - mUploadIntent = new Intent(mLatinIME, UploaderService.class); mUploadNowIntent = new Intent(mLatinIME, UploaderService.class); mUploadNowIntent.putExtra(UploaderService.EXTRA_UPLOAD_UNCONDITIONALLY, true); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { @@ -301,62 +297,19 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } } - private Dialog mSplashDialog = null; - private void maybeShowSplashScreen() { - if (ResearchSettings.readHasSeenSplash(mPrefs)) { - return; - } - if (mSplashDialog != null && mSplashDialog.isShowing()) { - return; - } - final IBinder windowToken = mMainKeyboardView != null - ? mMainKeyboardView.getWindowToken() : null; - if (windowToken == null) { - return; - } - final AlertDialog.Builder builder = new AlertDialog.Builder(mLatinIME) - .setTitle(R.string.research_splash_title) - .setMessage(R.string.research_splash_content) - .setPositiveButton(android.R.string.yes, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - onUserLoggingConsent(); - mSplashDialog.dismiss(); - } - }) - .setNegativeButton(android.R.string.no, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final String packageName = mLatinIME.getPackageName(); - final Uri packageUri = Uri.parse("package:" + packageName); - final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, - packageUri); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mLatinIME.startActivity(intent); - } - }) - .setCancelable(true) - .setOnCancelListener( - new OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - mLatinIME.requestHideSelf(0); - } - }); - mSplashDialog = builder.create(); - final Window w = mSplashDialog.getWindow(); - final WindowManager.LayoutParams lp = w.getAttributes(); - lp.token = windowToken; - lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; - w.setAttributes(lp); - w.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); - mSplashDialog.show(); - } - - public void onUserLoggingConsent() { + 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; @@ -367,12 +320,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang restart(); } - private void setLoggingAllowed(final boolean enableLogging) { - if (mPrefs == null) return; - sIsLogging = enableLogging; - ResearchSettings.writeResearchLoggerEnabledFlag(mPrefs, enableLogging); - } - private void checkForEmptyEditor() { if (mLatinIME == null) { return; @@ -429,6 +376,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang mMainResearchLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS); resetLogBuffers(); + cancelFeedbackDialog(); } public void abort() { @@ -457,6 +405,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } public void onResearchKeySelected(final LatinIME latinIME) { + mCurrentLogUnit.removeResearchButtonInvocation(); if (mInFeedbackDialog) { Toast.makeText(latinIME, R.string.research_please_exit_feedback_form, Toast.LENGTH_LONG).show(); @@ -465,6 +414,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang presentFeedbackDialog(latinIME); } + public void presentFeedbackDialogFromSettings() { + if (mLatinIME != null) { + presentFeedbackDialog(mLatinIME); + } + } + public void presentFeedbackDialog(final LatinIME latinIME) { if (isMakingUserRecording()) { saveRecording(); @@ -583,8 +538,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang toast.show(); boolean isLogDeleted = abort(); final long currentTime = System.currentTimeMillis(); - final long resumeTime = currentTime + 1000 * 60 * - SUSPEND_DURATION_IN_MINUTES; + final long resumeTime = currentTime + + TimeUnit.MINUTES.toMillis(SUSPEND_DURATION_IN_MINUTES); suspendLoggingUntil(resumeTime); toast.cancel(); Toast.makeText(latinIME, R.string.research_notify_logging_suspended, @@ -676,7 +631,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang mMotionEventReader.readMotionEventData(mUserRecordingFile); mReplayer.replay(replayData, null); } - }, 1000); + }, TimeUnit.SECONDS.toMillis(1)); } if (FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD) { @@ -701,13 +656,19 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang mInFeedbackDialog = false; } + private void cancelFeedbackDialog() { + if (isMakingUserRecording()) { + cancelRecording(); + } + mInFeedbackDialog = false; + } + public void initSuggest(final Suggest suggest) { mSuggest = suggest; // MainLogBuffer now has an out-of-date Suggest object. Close down MainLogBuffer and create // a new one. if (mMainLogBuffer != null) { - stop(); - start(); + restart(); } } @@ -854,23 +815,22 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang // 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 - // restore it to 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) + // 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. + // 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) { - final String oldLogUnitWords = oldLogUnit.getWordsAsString(); // 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. - if (oldLogUnitWords != null && !oldLogUnitWords.equals( - scrubDigitsFromString(expectedWord))) { - return; - } + final String scrubbedExpectedWord = scrubDigitsFromString(expectedWord); + final String oldLogUnitWords = oldLogUnit.getWordsAsString(); + if (!TextUtils.equals(scrubbedExpectedWord, oldLogUnitWords)) return; } // Uncommit, merging if necessary. @@ -986,7 +946,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang return Character.isDigit(codePoint) ? DIGIT_REPLACEMENT_CODEPOINT : codePoint; } - /* package for test */ static String scrubDigitsFromString(String s) { + /* 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)) { @@ -1109,22 +1070,24 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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, final int action, - final long eventTime, final int index, final int id, final int x, final int y) { - if (me != null) { - 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(); - } + 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(); } } @@ -1255,7 +1218,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final RichInputConnection connection) { String word = ""; if (connection != null) { - Range range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1); + TextRange range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1); if (range != null) { word = range.mWord.toString(); } @@ -1295,10 +1258,24 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang */ private static final LogStatement LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY = new LogStatement("LatinIMEPickSuggestionManually", true, false, "replacedWord", "index", - "suggestion", "x", "y", "isBatchMode"); + "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 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(); @@ -1307,8 +1284,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final String scrubbedWord = scrubDigitsFromString(suggestion); researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY, scrubDigitsFromString(replacedWord), index, - suggestion == null ? null : scrubbedWord, Constants.SUGGESTION_STRIP_COORDINATE, - Constants.SUGGESTION_STRIP_COORDINATE, isBatchMode); + 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()); } @@ -1437,7 +1414,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang "navigatePrevious", "clobberSettingsKey", "passwordInput", "shortcutKeyEnabled", "hasShortcutKey", "languageSwitchKeyEnabled", "isMultiLine", "tw", "th", "keys"); - public static void mainKeyboardView_setKeyboard(final Keyboard keyboard) { + 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(); @@ -1445,11 +1423,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang researchLogger.enqueueEvent(LOGSTATEMENT_MAINKEYBOARDVIEW_SETKEYBOARD, KeyboardId.elementIdToName(kid.mElementId), kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET), - kid.mOrientation, kid.mWidth, KeyboardId.modeName(kid.mMode), kid.imeAction(), + orientation, kid.mWidth, KeyboardId.modeName(kid.mMode), kid.imeAction(), kid.navigateNext(), kid.navigatePrevious(), kid.mClobberSettingsKey, isPasswordView, kid.mShortcutKeyEnabled, kid.mHasShortcutKey, kid.mLanguageSwitchKeyEnabled, kid.isMultiLine(), keyboard.mOccupiedWidth, - keyboard.mOccupiedHeight, keyboard.mKeys); + keyboard.mOccupiedHeight, keyboard.getKeys()); } /** @@ -1533,16 +1511,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang Constants.printableCode(scrubDigitFromCodePoint(code)), outputText == null ? null : scrubDigitsFromString(outputText.toString()), x, y, ignoreModifierKey, altersCode, key.isEnabled()); - if (code == Constants.CODE_RESEARCH) { - researchLogger.suppressResearchKeyMotionData(); - } } } - private void suppressResearchKeyMotionData() { - mCurrentLogUnit.removeResearchButtonInvocation(); - } - /** * Log a call to PointerTracker.callListenerCallListenerOnRelease(). * @@ -1794,7 +1765,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang public static void suddenJumpingTouchEventHandler_onTouchEvent(final MotionEvent me) { if (me != null) { getInstance().enqueueEvent(LOGSTATEMENT_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT, - me.toString()); + MotionEvent.obtain(me)); } } @@ -1830,7 +1801,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang */ private static final LogStatement LOGSTATEMENT_LATINIME_ONENDBATCHINPUT = new LogStatement("LatinIMEOnEndBatchInput", true, false, "enteredText", - "enteredWordPos"); + "enteredWordPos", "suggestedWords"); public static void latinIME_onEndBatchInput(final CharSequence enteredText, final int enteredWordPos, final SuggestedWords suggestedWords) { final ResearchLogger researchLogger = getInstance(); @@ -1838,7 +1809,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang researchLogger.mCurrentLogUnit.setWords(enteredText.toString()); } researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONENDBATCHINPUT, enteredText, - enteredWordPos); + enteredWordPos, suggestedWords); researchLogger.mCurrentLogUnit.initializeSuggestions(suggestedWords); researchLogger.mStatistics.recordGestureInput(enteredText.length(), SystemClock.uptimeMillis()); |