diff options
Diffstat (limited to 'java/src/com/android/inputmethod/research')
12 files changed, 239 insertions, 179 deletions
diff --git a/java/src/com/android/inputmethod/research/LogStatement.java b/java/src/com/android/inputmethod/research/LogStatement.java index 09b12fcfa..06b918af5 100644 --- a/java/src/com/android/inputmethod/research/LogStatement.java +++ b/java/src/com/android/inputmethod/research/LogStatement.java @@ -37,7 +37,8 @@ import java.io.IOException; */ public class LogStatement { private static final String TAG = LogStatement.class.getSimpleName(); - private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG; + 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 = diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java index 839e2b7ba..1c01675bd 100644 --- a/java/src/com/android/inputmethod/research/LogUnit.java +++ b/java/src/com/android/inputmethod/research/LogUnit.java @@ -46,7 +46,8 @@ import java.util.List; */ public class LogUnit { private static final String TAG = LogUnit.class.getSimpleName(); - private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG; + private static final boolean DEBUG = false + && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; private final ArrayList<LogStatement> mLogStatementList; private final ArrayList<Object[]> mValuesList; @@ -119,22 +120,6 @@ public class LogUnit { */ public synchronized void publishTo(final ResearchLog researchLog, final boolean canIncludePrivateData) { - // Prepare debugging output if necessary - final StringWriter debugStringWriter; - final JsonWriter debugJsonWriter; - if (DEBUG) { - debugStringWriter = new StringWriter(); - debugJsonWriter = new JsonWriter(debugStringWriter); - debugJsonWriter.setIndent(" "); - try { - debugJsonWriter.beginArray(); - } catch (IOException e) { - Log.e(TAG, "Could not open array in JsonWriter", e); - } - } else { - debugStringWriter = null; - debugJsonWriter = null; - } // Write out any logStatement that passes the privacy filter. final int size = mLogStatementList.size(); if (size != 0) { @@ -157,29 +142,12 @@ public class LogUnit { outputLogUnitStart(jsonWriter, canIncludePrivateData); } logStatement.outputToLocked(jsonWriter, mTimeList.get(i), mValuesList.get(i)); - if (DEBUG) { - logStatement.outputToLocked(debugJsonWriter, mTimeList.get(i), - mValuesList.get(i)); - } } if (jsonWriter != null) { // We must have called logUnitStart earlier, so emit a logUnitStop. outputLogUnitStop(jsonWriter); } } - if (DEBUG) { - try { - debugJsonWriter.endArray(); - debugJsonWriter.flush(); - } catch (IOException e) { - Log.e(TAG, "Could not close array in JsonWriter", e); - } - final String bigString = debugStringWriter.getBuffer().toString(); - final String[] lines = bigString.split("\n"); - for (String line : lines) { - Log.d(TAG, line); - } - } } private static final String WORD_KEY = "_wo"; diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java index 9aa60f859..3303d2bdb 100644 --- a/java/src/com/android/inputmethod/research/MainLogBuffer.java +++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java @@ -60,7 +60,8 @@ import java.util.Random; */ public abstract class MainLogBuffer extends FixedLogBuffer { private static final String TAG = MainLogBuffer.class.getSimpleName(); - private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG; + private static final boolean DEBUG = false + && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; // 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; diff --git a/java/src/com/android/inputmethod/research/MotionEventReader.java b/java/src/com/android/inputmethod/research/MotionEventReader.java index e59adfa19..e1cc2da73 100644 --- a/java/src/com/android/inputmethod/research/MotionEventReader.java +++ b/java/src/com/android/inputmethod/research/MotionEventReader.java @@ -34,7 +34,8 @@ import java.util.ArrayList; public class MotionEventReader { private static final String TAG = MotionEventReader.class.getSimpleName(); - private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG; + 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 diff --git a/java/src/com/android/inputmethod/research/Replayer.java b/java/src/com/android/inputmethod/research/Replayer.java index a9b7a9d0c..903875f3c 100644 --- a/java/src/com/android/inputmethod/research/Replayer.java +++ b/java/src/com/android/inputmethod/research/Replayer.java @@ -37,7 +37,8 @@ import com.android.inputmethod.research.MotionEventReader.ReplayData; */ public class Replayer { private static final String TAG = Replayer.class.getSimpleName(); - private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG; + 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; diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java index 9016e23b3..35a491f2c 100644 --- a/java/src/com/android/inputmethod/research/ResearchLog.java +++ b/java/src/com/android/inputmethod/research/ResearchLog.java @@ -20,11 +20,11 @@ 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.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -47,14 +47,15 @@ import java.util.concurrent.TimeUnit; * 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#IS_EXPERIMENTAL}. + * 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.IS_EXPERIMENTAL_DEBUG; + 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 int ABORT_TIMEOUT_IN_MS = 1000 * 4; /* package */ final ScheduledExecutorService mExecutor; /* package */ final File mFile; @@ -99,8 +100,10 @@ public class ResearchLog { * output. * * See class comment for details about {@code JsonWriter} construction. + * + * @param onClosed run after the close() operation has completed asynchronously */ - public synchronized void close(final Runnable onClosed) { + private synchronized void close(final Runnable onClosed) { mExecutor.submit(new Callable<Object>() { @Override public Object call() throws Exception { @@ -117,6 +120,8 @@ public class ResearchLog { } catch (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); } @@ -131,15 +136,24 @@ public class ResearchLog { mExecutor.shutdown(); } - private boolean mIsAbortSuccessful; + /** + * 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 {@link JsonWriter}, but then deletes the - * backing file used for output. + * Waits for publication requests to finish, closes the JsonWriter, but then deletes the backing + * output file. * - * See class comment for details about {@code JsonWriter} construction. + * @param onAbort run after the abort() operation has completed asynchronously */ - public synchronized void abort() { + private synchronized void abort(final Runnable onAbort) { mExecutor.submit(new Callable<Object>() { @Override public Object call() throws Exception { @@ -151,7 +165,10 @@ public class ResearchLog { } } finally { if (mFile != null) { - mIsAbortSuccessful = mFile.delete(); + mFile.delete(); + } + if (onAbort != null) { + onAbort.run(); } } return null; @@ -161,14 +178,25 @@ public class ResearchLog { mExecutor.shutdown(); } - public boolean blockingAbort() throws InterruptedException { - abort(); - mExecutor.awaitTermination(ABORT_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS); - return mIsAbortSuccessful; + /** + * 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); } - public void awaitTermination(int delay, TimeUnit timeUnit) throws InterruptedException { - mExecutor.awaitTermination(delay, timeUnit); + @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() { @@ -214,10 +242,10 @@ public class ResearchLog { return null; } }); - } catch (RejectedExecutionException e) { + } catch (final RejectedExecutionException e) { // TODO: Add code to record loss of data, and report. if (DEBUG) { - Log.d(TAG, "ResearchLog.publish() rejecting scheduled execution"); + Log.d(TAG, "ResearchLog.publish() rejecting scheduled execution", e); } } } diff --git a/java/src/com/android/inputmethod/research/ResearchLogDirectory.java b/java/src/com/android/inputmethod/research/ResearchLogDirectory.java new file mode 100644 index 000000000..291dea5d0 --- /dev/null +++ b/java/src/com/android/inputmethod/research/ResearchLogDirectory.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; + +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) { + return new File(mFilesDir, getUniqueFilename(LOG_FILENAME_PREFIX, time)); + } + + public File getUserRecordingFilePath(final long time) { + return new File(mFilesDir, getUniqueFilename(USER_RECORDING_FILENAME_PREFIX, time)); + } + + private static String getUniqueFilename(final String prefix, final long time) { + return prefix + "-" + time + FILENAME_SUFFIX; + } +} diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index e705ddda1..a38a226f0 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -97,7 +97,8 @@ import java.util.UUID; * 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#IS_EXPERIMENTAL}. + * This functionality is off by default. See + * {@link ProductionFlag#USES_DEVELOPMENT_ONLY_DIAGNOSTICS}. */ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener { // TODO: This class has grown quite large and combines several concerns that should be @@ -109,13 +110,14 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang // 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.IS_EXPERIMENTAL_DEBUG; + private static final boolean DEBUG = false + && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; private static final boolean DEBUG_REPLAY_AFTER_FEEDBACK = false - && ProductionFlag.IS_EXPERIMENTAL_DEBUG; + && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; // Whether the TextView contents are logged at the end of the session. true will disclose // private info. private static final boolean LOG_FULL_TEXTVIEW_CONTENTS = false - && ProductionFlag.IS_EXPERIMENTAL_DEBUG; + && 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 @@ -125,16 +127,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang /* package */ static boolean sIsLogging = false; private static final int OUTPUT_FORMAT_VERSION = 5; private static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode"; - /* package */ static final String LOG_FILENAME_PREFIX = "researchLog"; - private static final String LOG_FILENAME_SUFFIX = ".txt"; - /* package */ static final String USER_RECORDING_FILENAME_PREFIX = "recording"; - private static final String USER_RECORDING_FILENAME_SUFFIX = ".txt"; - private static final SimpleDateFormat TIMESTAMP_DATEFORMAT = - new SimpleDateFormat("yyyyMMddHHmmssS", Locale.US); // 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.IS_EXPERIMENTAL_DEBUG; + && 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); @@ -145,7 +141,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang // 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.IS_EXPERIMENTAL_DEBUG); + (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; @@ -154,11 +150,14 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 ResearchLogger sInstance = new ResearchLogger(); private static String sAccountType = null; private static String sAllowedAccountDomain = null; - // to write to a different filename, e.g., for testing, set mFile before calling start() - /* package */ File mFilesDir; /* package */ ResearchLog mMainResearchLog; // mFeedbackLog records all events for the session, private or not (excepting // passwords). It is written to permanent storage only if the user explicitly commands @@ -184,9 +183,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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"; - private static final String PREF_LAST_CLEANUP_TIME = "pref_last_cleanup_time"; - 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; protected static final int SUSPEND_DURATION_IN_MINUTES = 1; // set when LatinIME should ignore an onUpdateSelection() callback that // arises from operations in this class @@ -200,6 +196,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private final Statistics mStatistics; private final MotionEventReader mMotionEventReader = new MotionEventReader(); private final Replayer mReplayer = Replayer.getInstance(); + private ResearchLogDirectory mResearchLogDirectory; private Intent mUploadIntent; private Intent mUploadNowIntent; @@ -237,11 +234,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final Suggest suggest) { assert latinIME != null; mLatinIME = latinIME; - mFilesDir = latinIME.getFilesDir(); - if (mFilesDir == null || !mFilesDir.exists()) { - Log.w(TAG, "IME storage directory does not exist. Cannot start logging."); - return; - } mPrefs = PreferenceManager.getDefaultSharedPreferences(latinIME); mPrefs.registerOnSharedPreferenceChangeListener(this); @@ -253,26 +245,29 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang sAccountType = res.getString(R.string.research_account_type); sAllowedAccountDomain = res.getString(R.string.research_allowed_account_domain); - // Cleanup logging directory - // TODO: Move this and other file-related components to separate file. - final long lastCleanupTime = mPrefs.getLong(PREF_LAST_CLEANUP_TIME, 0L); - final long now = System.currentTimeMillis(); - if (now - lastCleanupTime > DURATION_BETWEEN_DIR_CLEANUP_IN_MS) { - final long timeHorizon = now - MAX_LOGFILE_AGE_IN_MS; - cleanupLoggingDir(mFilesDir, timeHorizon); - mPrefs.edit().putLong(PREF_LAST_CLEANUP_TIME, now).apply(); - } + // Initialize directory manager + mResearchLogDirectory = new ResearchLogDirectory(mLatinIME); + cleanLogDirectoryIfNeeded(mResearchLogDirectory, System.currentTimeMillis()); // Initialize external services mUploadIntent = new Intent(mLatinIME, UploaderService.class); mUploadNowIntent = new Intent(mLatinIME, UploaderService.class); mUploadNowIntent.putExtra(UploaderService.EXTRA_UPLOAD_UNCONDITIONALLY, true); - if (ProductionFlag.IS_EXPERIMENTAL) { + if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { scheduleUploadingService(mLatinIME); } mReplayer.setKeyboardSwitcher(keyboardSwitcher); } + 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); + } + /** * Arrange for the UploaderService to be run on a regular basis. * @@ -292,17 +287,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang UploaderService.RUN_INTERVAL, UploaderService.RUN_INTERVAL, pendingIntent); } - private void cleanupLoggingDir(final File dir, final long time) { - for (File file : dir.listFiles()) { - final String filename = file.getName(); - if ((filename.startsWith(ResearchLogger.LOG_FILENAME_PREFIX) - || filename.startsWith(ResearchLogger.USER_RECORDING_FILENAME_PREFIX)) - && file.lastModified() < time) { - file.delete(); - } - } - } - public void mainKeyboardView_onAttachedToWindow(final MainKeyboardView mainKeyboardView) { mMainKeyboardView = mainKeyboardView; maybeShowSplashScreen(); @@ -384,35 +368,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang restart(); } - private static int sLogFileCounter = 0; - - private File createLogFile(final File filesDir) { - final StringBuilder sb = new StringBuilder(); - sb.append(LOG_FILENAME_PREFIX).append('-'); - final String uuid = ResearchSettings.readResearchLoggerUuid(mPrefs); - sb.append(uuid).append('-'); - sb.append(TIMESTAMP_DATEFORMAT.format(new Date())).append('-'); - // Sometimes logFiles are created within milliseconds of each other. Append a counter to - // separate these. - if (sLogFileCounter < Integer.MAX_VALUE) { - sLogFileCounter++; - } else { - // Wrap the counter, in the unlikely event of overflow. - sLogFileCounter = 0; - } - sb.append(sLogFileCounter); - sb.append(LOG_FILENAME_SUFFIX); - return new File(filesDir, sb.toString()); - } - - private File createUserRecordingFile(final File filesDir) { - final StringBuilder sb = new StringBuilder(); - sb.append(USER_RECORDING_FILENAME_PREFIX).append('-'); - final String uuid = ResearchSettings.readResearchLoggerUuid(mPrefs); - sb.append(uuid).append('-'); - sb.append(TIMESTAMP_DATEFORMAT.format(new Date())); - sb.append(USER_RECORDING_FILENAME_SUFFIX); - return new File(filesDir, sb.toString()); + private void setLoggingAllowed(final boolean enableLogging) { + if (mPrefs == null) return; + sIsLogging = enableLogging; + ResearchSettings.writeResearchLoggerEnabledFlag(mPrefs, enableLogging); } private void checkForEmptyEditor() { @@ -452,7 +411,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang return; } if (mMainLogBuffer == null) { - mMainResearchLog = new ResearchLog(createLogFile(mFilesDir), mLatinIME); + mMainResearchLog = new ResearchLog(mResearchLogDirectory.getLogFilePath( + System.currentTimeMillis()), mLatinIME); final int numWordsToIgnore = new Random().nextInt(NUMBER_OF_WORDS_BETWEEN_SAMPLES + 1); mMainLogBuffer = new MainLogBuffer(NUMBER_OF_WORDS_BETWEEN_SAMPLES, numWordsToIgnore, mSuggest) { @@ -485,7 +445,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } private void resetFeedbackLogging() { - mFeedbackLog = new ResearchLog(createLogFile(mFilesDir), mLatinIME); + mFeedbackLog = new ResearchLog(mResearchLogDirectory.getLogFilePath( + System.currentTimeMillis()), mLatinIME); mFeedbackLogBuffer = new FixedLogBuffer(FEEDBACK_WORD_BUFFER_SIZE); } @@ -502,42 +463,29 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang commitCurrentLogUnit(); mMainLogBuffer.setIsStopping(); mMainLogBuffer.shiftAndPublishAll(); - mMainResearchLog.close(null /* callback */); + mMainResearchLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS); mMainLogBuffer = null; } if (mFeedbackLogBuffer != null) { - mFeedbackLog.close(null /* callback */); + mFeedbackLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS); mFeedbackLogBuffer = null; } } - public boolean abort() { + public void abort() { if (DEBUG) { Log.d(TAG, "abort called"); } - boolean didAbortMainLog = false; if (mMainLogBuffer != null) { mMainLogBuffer.clear(); - try { - didAbortMainLog = mMainResearchLog.blockingAbort(); - } catch (InterruptedException e) { - // Don't know whether this succeeded or not. We assume not; this is reported - // to the caller. - } + mMainResearchLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS); mMainLogBuffer = null; } - boolean didAbortFeedbackLog = false; if (mFeedbackLogBuffer != null) { mFeedbackLogBuffer.clear(); - try { - didAbortFeedbackLog = mFeedbackLog.blockingAbort(); - } catch (InterruptedException e) { - // Don't know whether this succeeded or not. We assume not; this is reported - // to the caller. - } + mFeedbackLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS); mFeedbackLogBuffer = null; } - return didAbortMainLog && didAbortFeedbackLog; } private void restart() { @@ -620,9 +568,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private void startRecordingInternal() { if (mUserRecordingLog != null) { - mUserRecordingLog.abort(); + mUserRecordingLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS); } - mUserRecordingFile = createUserRecordingFile(mFilesDir); + mUserRecordingFile = mResearchLogDirectory.getUserRecordingFilePath( + System.currentTimeMillis()); mUserRecordingLog = new ResearchLog(mUserRecordingFile, mLatinIME); mUserRecordingLogBuffer = new LogBuffer(); resetRecordingTimer(); @@ -658,7 +607,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private void cancelRecording() { if (mUserRecordingLog != null) { - mUserRecordingLog.abort(); + mUserRecordingLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS); } mUserRecordingLog = null; mUserRecordingLogBuffer = null; @@ -670,7 +619,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private void saveRecording() { commitCurrentLogUnit(); publishLogBuffer(mUserRecordingLogBuffer, mUserRecordingLog, true); - mUserRecordingLog.close(null); + mUserRecordingLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS); mUserRecordingLog = null; mUserRecordingLogBuffer = null; @@ -782,12 +731,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang feedbackContents, accountName, recording); mFeedbackLogBuffer.shiftIn(feedbackLogUnit); publishLogBuffer(mFeedbackLogBuffer, mSavedFeedbackLog, true /* isIncludingPrivateData */); - mSavedFeedbackLog.close(new Runnable() { - @Override - public void run() { - uploadNow(); - } - }); + mSavedFeedbackLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS); + uploadNow(); if (isIncludingRecording && DEBUG_REPLAY_AFTER_FEEDBACK) { final Handler handler = new Handler(); @@ -1148,7 +1093,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang new LogStatement("LatinImeOnStartInputViewInternal", false, false, "uuid", "packageName", "inputType", "imeOptions", "fieldId", "display", "model", "prefs", "versionCode", "versionName", "outputFormatVersion", "logEverything", - "isExperimentalDebug"); + "isUsingDevelopmentOnlyDiagnosticsDebug"); public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo, final SharedPreferences prefs) { final ResearchLogger researchLogger = getInstance(); @@ -1170,7 +1115,7 @@ 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.IS_EXPERIMENTAL_DEBUG); + ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG); } catch (NameNotFoundException e) { e.printStackTrace(); } diff --git a/java/src/com/android/inputmethod/research/ResearchSettings.java b/java/src/com/android/inputmethod/research/ResearchSettings.java index 11e9ac77a..c0bc03fde 100644 --- a/java/src/com/android/inputmethod/research/ResearchSettings.java +++ b/java/src/com/android/inputmethod/research/ResearchSettings.java @@ -26,6 +26,8 @@ public final class ResearchSettings { "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. @@ -58,4 +60,13 @@ public final class ResearchSettings { 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 index 50e2b7fbc..7f6c851bb 100644 --- a/java/src/com/android/inputmethod/research/Statistics.java +++ b/java/src/com/android/inputmethod/research/Statistics.java @@ -23,7 +23,8 @@ import com.android.inputmethod.latin.define.ProductionFlag; public class Statistics { private static final String TAG = Statistics.class.getSimpleName(); - private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG; + 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 diff --git a/java/src/com/android/inputmethod/research/Uploader.java b/java/src/com/android/inputmethod/research/Uploader.java index df495a88d..ba05ec12b 100644 --- a/java/src/com/android/inputmethod/research/Uploader.java +++ b/java/src/com/android/inputmethod/research/Uploader.java @@ -17,7 +17,6 @@ package com.android.inputmethod.research; import android.Manifest; -import android.app.AlarmManager; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -33,7 +32,6 @@ import com.android.inputmethod.latin.define.ProductionFlag; import java.io.BufferedReader; import java.io.File; -import java.io.FileFilter; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; @@ -48,19 +46,20 @@ import java.net.URL; */ public final class Uploader { private static final String TAG = Uploader.class.getSimpleName(); - private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG; + 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_AUTO_UPLOAD = false - && ProductionFlag.IS_EXPERIMENTAL_DEBUG; // Force false for non-debug builds + && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; private static final int BUF_SIZE = 1024 * 8; private final Context mContext; - private final File mFilesDir; + private final ResearchLogDirectory mResearchLogDirectory; private final URL mUrl; public Uploader(final Context context) { mContext = context; - mFilesDir = context.getFilesDir(); + mResearchLogDirectory = new ResearchLogDirectory(context); final String urlString = context.getString(R.string.research_logger_upload_url); if (TextUtils.isEmpty(urlString)) { @@ -106,16 +105,8 @@ public final class Uploader { } public void doUpload() { - if (mFilesDir == null) { - return; - } - final File[] files = mFilesDir.listFiles(new FileFilter() { - @Override - public boolean accept(final File pathname) { - return pathname.getName().startsWith(ResearchLogger.LOG_FILENAME_PREFIX) - && !pathname.canWrite(); - } - }); + final File[] files = mResearchLogDirectory.getUploadableLogFiles(); + if (files == null) return; for (final File file : files) { uploadFile(file); } diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java index 26b651056..6a9f5c1f4 100644 --- a/java/src/com/android/inputmethod/research/UploaderService.java +++ b/java/src/com/android/inputmethod/research/UploaderService.java @@ -30,7 +30,8 @@ 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.IS_EXPERIMENTAL_DEBUG; + 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"; |