diff options
Diffstat (limited to 'java/src/com/android/inputmethod/research')
4 files changed, 93 insertions, 50 deletions
diff --git a/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java index c5f095919..4f86526a7 100644 --- a/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java +++ b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java @@ -25,9 +25,10 @@ import android.content.Intent; */ public final class BootBroadcastReceiver extends BroadcastReceiver { @Override - public void onReceive(Context context, Intent intent) { + public void onReceive(final Context context, final Intent intent) { if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { - ResearchLogger.scheduleUploadingService(context); + UploaderService.cancelAndRescheduleUploadingService(context, + true /* needsRescheduling */); } } } diff --git a/java/src/com/android/inputmethod/research/MotionEventReader.java b/java/src/com/android/inputmethod/research/MotionEventReader.java index e1cc2da73..fbfd9b531 100644 --- a/java/src/com/android/inputmethod/research/MotionEventReader.java +++ b/java/src/com/android/inputmethod/research/MotionEventReader.java @@ -22,6 +22,7 @@ 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; @@ -64,6 +65,7 @@ public class MotionEventReader { return replayData; } + @UsedForTesting static class ReplayData { final ArrayList<Integer> mActions = new ArrayList<Integer>(); final ArrayList<PointerProperties[]> mPointerPropertiesArrays @@ -134,6 +136,7 @@ public class MotionEventReader { * }, * </pre> */ + @UsedForTesting /* package for test */ void readLogStatement(final JsonReader jsonReader, final ReplayData replayData) throws IOException { String logStatementType = null; diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index aa3465e5a..7a23ddb05 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -20,16 +20,13 @@ import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOAR import android.accounts.Account; import android.accounts.AccountManager; -import android.app.AlarmManager; import android.app.AlertDialog; import android.app.Dialog; -import android.app.PendingIntent; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; @@ -74,22 +71,17 @@ import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.research.MotionEventReader.ReplayData; -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.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Date; import java.util.List; -import java.util.Locale; import java.util.Random; -import java.util.UUID; +import java.util.regex.Pattern; /** * Logs the use of the LatinIME keyboard. @@ -254,7 +246,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang mUploadNowIntent = new Intent(mLatinIME, UploaderService.class); mUploadNowIntent.putExtra(UploaderService.EXTRA_UPLOAD_UNCONDITIONALLY, true); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - scheduleUploadingService(mLatinIME); + UploaderService.cancelAndRescheduleUploadingService(mLatinIME, + true /* needsRescheduling */); } mReplayer.setKeyboardSwitcher(keyboardSwitcher); } @@ -268,25 +261,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang ResearchSettings.writeResearchLastDirCleanupTime(mPrefs, now); } - /** - * Arrange for the UploaderService to be run on a regular basis. - * - * Any existing scheduled invocation of UploaderService is removed and rescheduled. This may - * cause problems if this method is called often and frequent updates are required, but since - * the user will likely be sleeping at some point, if the interval is less that the expected - * sleep duration and this method is not called during that time, the service should be invoked - * at some point. - */ - public static void scheduleUploadingService(Context context) { - final Intent intent = new Intent(context, UploaderService.class); - final PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0); - final AlarmManager manager = - (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - manager.cancel(pendingIntent); - manager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, - UploaderService.RUN_INTERVAL, UploaderService.RUN_INTERVAL, pendingIntent); - } - public void mainKeyboardView_onAttachedToWindow(final MainKeyboardView mainKeyboardView) { mMainKeyboardView = mainKeyboardView; maybeShowSplashScreen(); @@ -790,8 +764,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } private boolean isAllowedToLog() { - return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging && !mInFeedbackDialog - && !isReplaying(); + return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging && !mInFeedbackDialog; } public void requestIndicatorRedraw() { @@ -1093,7 +1066,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang new LogStatement("LatinImeOnStartInputViewInternal", false, false, "uuid", "packageName", "inputType", "imeOptions", "fieldId", "display", "model", "prefs", "versionCode", "versionName", "outputFormatVersion", "logEverything", - "isUsingDevelopmentOnlyDiagnosticsDebug"); + "isDevTeamBuild"); public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo, final SharedPreferences prefs) { final ResearchLogger researchLogger = getInstance(); @@ -1115,15 +1088,30 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId, Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName, OUTPUT_FORMAT_VERSION, IS_LOGGING_EVERYTHING, - ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG); - } catch (NameNotFoundException e) { - e.printStackTrace(); + 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); } } } - public void latinIME_onFinishInputViewInternal() { - stop(); + // 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; + } } /** @@ -1208,16 +1196,22 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } /** - * Log a call to LatinIME.onWindowHidden(). + * 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_ONWINDOWHIDDEN = - new LogStatement("LatinIMEOnWindowHidden", false, false, "isTextTruncated", "text"); - public static void latinIME_onWindowHidden(final int savedSelectionStart, - final int savedSelectionEnd, final InputConnection ic) { - if (ic != null) { + private static final LogStatement LOGSTATEMENT_LATINIME_ONFINISHINPUTVIEWINTERNAL = + new LogStatement("LatinIMEOnFinishInputViewInternal", false, false, "isTextTruncated", + "text"); + public static void latinIME_onFinishInputViewInternal(final boolean finishingInput, + final int savedSelectionStart, final int savedSelectionEnd, final InputConnection ic) { + // 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 (ic != null && !finishingInput) { final boolean isTextTruncated; final String text; if (LOG_FULL_TEXTVIEW_CONTENTS) { @@ -1261,8 +1255,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang // 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_ONWINDOWHIDDEN, isTextTruncated, - text); + researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONFINISHINPUTVIEWINTERNAL, + isTextTruncated, text); researchLogger.commitCurrentLogUnit(); getInstance().stop(); } @@ -1289,7 +1283,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang if (connection != null) { Range range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1); if (range != null) { - word = range.mWord; + word = range.mWord.toString(); } } final ResearchLogger researchLogger = getInstance(); @@ -1634,8 +1628,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final String scrubbedAutoCorrection = scrubDigitsFromString(autoCorrection); final ResearchLogger researchLogger = getInstance(); researchLogger.mCurrentLogUnit.initializeSuggestions(suggestedWords); - researchLogger.commitCurrentLogUnitAsWord(scrubbedAutoCorrection, Long.MAX_VALUE, - isBatchMode); + 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 diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java index 6a9f5c1f4..6a9717b7c 100644 --- a/java/src/com/android/inputmethod/research/UploaderService.java +++ b/java/src/com/android/inputmethod/research/UploaderService.java @@ -18,6 +18,8 @@ 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; @@ -43,11 +45,17 @@ public final class UploaderService extends IntentService { @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) { @@ -57,4 +65,42 @@ public final class UploaderService extends IntentService { } 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 PendingIntent pendingIntent = getPendingIntentForService(context); + final AlarmManager alarmManager = (AlarmManager) context.getSystemService( + Context.ALARM_SERVICE); + cancelAnyScheduledServiceAlarm(alarmManager, pendingIntent); + if (needsRescheduling) { + scheduleServiceAlarm(alarmManager, pendingIntent); + } + } + + private static PendingIntent getPendingIntentForService(final Context context) { + final Intent intent = new Intent(context, UploaderService.class); + return PendingIntent.getService(context, 0, intent, 0); + } + + private static void cancelAnyScheduledServiceAlarm(final AlarmManager alarmManager, + final PendingIntent pendingIntent) { + alarmManager.cancel(pendingIntent); + } + + private static void scheduleServiceAlarm(final AlarmManager alarmManager, + final PendingIntent pendingIntent) { + alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, UploaderService.RUN_INTERVAL, + pendingIntent); + } } |