diff options
Diffstat (limited to 'java/src/com/android/inputmethod/research')
11 files changed, 230 insertions, 165 deletions
diff --git a/java/src/com/android/inputmethod/research/FeedbackActivity.java b/java/src/com/android/inputmethod/research/FeedbackActivity.java index b985fda21..520b88d2f 100644 --- a/java/src/com/android/inputmethod/research/FeedbackActivity.java +++ b/java/src/com/android/inputmethod/research/FeedbackActivity.java @@ -18,7 +18,6 @@ package com.android.inputmethod.research; import android.app.Activity; import android.os.Bundle; -import android.widget.CheckBox; import com.android.inputmethod.latin.R; diff --git a/java/src/com/android/inputmethod/research/FeedbackFragment.java b/java/src/com/android/inputmethod/research/FeedbackFragment.java index a0738292e..75fbbf1ba 100644 --- a/java/src/com/android/inputmethod/research/FeedbackFragment.java +++ b/java/src/com/android/inputmethod/research/FeedbackFragment.java @@ -16,7 +16,6 @@ package com.android.inputmethod.research; -import android.app.Activity; import android.app.Fragment; import android.os.Bundle; import android.text.Editable; diff --git a/java/src/com/android/inputmethod/research/JsonUtils.java b/java/src/com/android/inputmethod/research/JsonUtils.java index 24cd8d935..2beebdfae 100644 --- a/java/src/com/android/inputmethod/research/JsonUtils.java +++ b/java/src/com/android/inputmethod/research/JsonUtils.java @@ -75,12 +75,12 @@ import java.util.Map; private static void writeJson(final Key key, final JsonWriter jsonWriter) throws IOException { jsonWriter.beginObject(); - jsonWriter.name("code").value(key.mCode); + jsonWriter.name("code").value(key.getCode()); jsonWriter.name("altCode").value(key.getAltCode()); - jsonWriter.name("x").value(key.mX); - jsonWriter.name("y").value(key.mY); - jsonWriter.name("w").value(key.mWidth); - jsonWriter.name("h").value(key.mHeight); + 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(); } @@ -94,12 +94,17 @@ import java.util.Map; .value(words.mIsPunctuationSuggestions); jsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions); jsonWriter.name("isPrediction").value(words.mIsPrediction); - jsonWriter.name("words"); + jsonWriter.name("suggestedWords"); jsonWriter.beginArray(); final int size = words.size(); for (int j = 0; j < size; j++) { final SuggestedWordInfo wordInfo = words.getInfo(j); - jsonWriter.value(wordInfo.toString()); + jsonWriter.beginObject(); + jsonWriter.name("word").value(wordInfo.toString()); + jsonWriter.name("score").value(wordInfo.mScore); + jsonWriter.name("kind").value(wordInfo.mKind); + jsonWriter.name("sourceDict").value(wordInfo.mSourceDict.mDictType); + jsonWriter.endObject(); } jsonWriter.endArray(); jsonWriter.endObject(); diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java index 164c7e8cc..3366df12a 100644 --- a/java/src/com/android/inputmethod/research/LogUnit.java +++ b/java/src/com/android/inputmethod/research/LogUnit.java @@ -146,7 +146,8 @@ public class LogUnit { 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 = null; + JsonWriter jsonWriter = researchLog.getInitializedJsonWriterLocked(); + outputLogUnitStart(jsonWriter, canIncludePrivateData); for (int i = 0; i < size; i++) { final LogStatement logStatement = mLogStatementList.get(i); if (!canIncludePrivateData && logStatement.isPotentiallyPrivate()) { @@ -155,42 +156,35 @@ public class LogUnit { if (mIsPartOfMegaword && logStatement.isPotentiallyRevealing()) { continue; } - // Only retrieve the jsonWriter if we need to. If we don't get this far, then - // researchLog.getInitializedJsonWriterLocked() will not ever be called, and the - // file will not have been opened for writing. - if (jsonWriter == null) { - jsonWriter = researchLog.getInitializedJsonWriterLocked(); - outputLogUnitStart(jsonWriter, canIncludePrivateData); - } logStatement.outputToLocked(jsonWriter, mTimeList.get(i), mValuesList.get(i)); } - if (jsonWriter != null) { - // We must have called logUnitStart earlier, so emit a logUnitStop. - outputLogUnitStop(jsonWriter); - } + 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); + 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 */); + 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()); + SystemClock.uptimeMillis(), getWordsAsString(), getCorrectionType(), + getNumWords()); } else { LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA.outputToLocked(jsonWriter, - SystemClock.uptimeMillis()); + SystemClock.uptimeMillis(), getNumWords()); } } diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java index 3482153b4..6df7c1708 100644 --- a/java/src/com/android/inputmethod/research/MainLogBuffer.java +++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java @@ -119,9 +119,9 @@ public abstract class MainLogBuffer extends FixedLogBuffer { * * @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 + * {@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 + * 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. diff --git a/java/src/com/android/inputmethod/research/MotionEventReader.java b/java/src/com/android/inputmethod/research/MotionEventReader.java index fbfd9b531..3388645b7 100644 --- a/java/src/com/android/inputmethod/research/MotionEventReader.java +++ b/java/src/com/android/inputmethod/research/MotionEventReader.java @@ -315,16 +315,6 @@ public class MotionEventReader { return pointerCoords; } - /** - * Tests that {@code x} is uninitialized. - * - * Assumes that {@code x} will never be given a valid value less than 0, and that - * UNINITIALIZED_FLOAT is less than 0.0f. - */ - private boolean isUninitializedFloat(final float x) { - return x < 0.0f; - } - private void addMotionEventData(final ReplayData replayData, final int actionType, final long time, final PointerProperties[] pointerProperties, final PointerCoords[] pointerCoords) { diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java index fde2798e1..46e620ae5 100644 --- a/java/src/com/android/inputmethod/research/ResearchLog.java +++ b/java/src/com/android/inputmethod/research/ResearchLog.java @@ -27,7 +27,6 @@ import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.concurrent.Callable; import java.util.concurrent.Executors; @@ -56,7 +55,7 @@ public class ResearchLog { 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 = 1000 * 5; + private static final long FLUSH_DELAY_IN_MS = TimeUnit.SECONDS.toMillis(5); /* package */ final ScheduledExecutorService mExecutor; /* package */ final File mFile; diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index aa4a866b8..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(); } } @@ -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()); diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java index e573ca012..fd323a104 100644 --- a/java/src/com/android/inputmethod/research/Statistics.java +++ b/java/src/com/android/inputmethod/research/Statistics.java @@ -21,6 +21,8 @@ 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 @@ -102,8 +104,8 @@ public class Statistics { // 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 int MIN_TYPING_INTERMISSION = 2 * 1000; // in milliseconds - public static final int MIN_DELETION_INTERMISSION = 10 * 1000; // in milliseconds + 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; diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java index d2db34927..fd3f2f60e 100644 --- a/java/src/com/android/inputmethod/research/UploaderService.java +++ b/java/src/com/android/inputmethod/research/UploaderService.java @@ -24,8 +24,6 @@ import android.content.Intent; import android.os.Bundle; import android.os.SystemClock; -import com.android.inputmethod.latin.define.ProductionFlag; - /** * Service to invoke the uploader. * @@ -33,12 +31,9 @@ import com.android.inputmethod.latin.define.ProductionFlag; */ public final class UploaderService extends IntentService { private static final String TAG = UploaderService.class.getSimpleName(); - private static final boolean DEBUG = false - && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; public static final long RUN_INTERVAL = AlarmManager.INTERVAL_HOUR; public static final String EXTRA_UPLOAD_UNCONDITIONALLY = UploaderService.class.getName() + ".extra.UPLOAD_UNCONDITIONALLY"; - protected static final int TIMEOUT_IN_MS = 1000 * 4; public UploaderService() { super("Research Uploader Service"); diff --git a/java/src/com/android/inputmethod/research/ui/SplashScreen.java b/java/src/com/android/inputmethod/research/ui/SplashScreen.java new file mode 100644 index 000000000..78ed668d1 --- /dev/null +++ b/java/src/com/android/inputmethod/research/ui/SplashScreen.java @@ -0,0 +1,111 @@ +/* + * 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(); + } +} |