Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 48ded4e3 authored by Kurt Partridge's avatar Kurt Partridge Committed by Android (Google) Code Review
Browse files

Merge "ResearchLogger: make logging more reliable (esp on startup)"

parents 19ac19e5 0df48767
Loading
Loading
Loading
Loading
+3 −4
Original line number Diff line number Diff line
@@ -223,15 +223,15 @@
    <string name="notify_recorded_timestamp">Recorded timestamp</string>

    <!-- Title for dialog option to let users cancel logging and delete log for this session [CHAR LIMIT=35] -->
    <string name="do_not_log_this_session">Do not log this session</string>
    <string name="do_not_log_this_session">Suspend logging</string>
    <!-- Title for dialog option to let users reenable logging [CHAR LIMIT=35] -->
    <string name="enable_session_logging">Enable session logging</string>
    <string name="enable_session_logging">Enable logging</string>
    <!--  Title for dialog option to let users log all events in this session [CHAR LIMIT=35] -->
    <string name="log_whole_session_history">Log whole session history</string>
    <!-- Toast notification that the system is processing the request to delete the log for this session [CHAR LIMIT=35] -->
    <string name="notify_session_log_deleting">Deleting session log</string>
    <!-- Toast notification that the system has successfully deleted the log for this session [CHAR LIMIT=35] -->
    <string name="notify_session_log_deleted">Session log deleted</string>
    <string name="notify_logging_suspended">Logging temporarily suspended.  To disable permanently, go to Android Keyboard Settings</string>
    <!-- Toast notification that the system has failed to delete the log for this session [CHAR LIMIT=35] -->
    <string name="notify_session_log_not_deleted">Session log NOT deleted</string>
    <!-- Toast notification that the system has recorded the whole session history [CHAR LIMIT=35] -->
@@ -240,7 +240,6 @@
    <string name="notify_session_history_not_logged">Error: Session history NOT logged</string>
    <!-- Toast notification that the system is enabling logging [CHAR LIMIT=35] -->
    <string name="notify_session_logging_enabled">Session logging enabled</string>

    <!-- Preference for input language selection -->
    <string name="select_language">Input languages</string>

+1 −2
Original line number Diff line number Diff line
@@ -618,7 +618,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
                    + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0));
        }
        if (ProductionFlag.IS_EXPERIMENTAL) {
            ResearchLogger.getInstance().start();
            ResearchLogger.latinIME_onStartInputViewInternal(editorInfo, mPrefs);
        }
        if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) {
@@ -711,7 +710,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen

        LatinImeLogger.commit();
        if (ProductionFlag.IS_EXPERIMENTAL) {
            ResearchLogger.getInstance().stop();
            ResearchLogger.getInstance().latinIME_onFinishInputInternal();
        }

        KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
+31 −17
Original line number Diff line number Diff line
@@ -55,13 +55,14 @@ public class ResearchLog {

    final ScheduledExecutorService mExecutor;
    /* package */ final File mFile;
    private JsonWriter mJsonWriter = NULL_JSON_WRITER; // should never be null
    private JsonWriter mJsonWriter = NULL_JSON_WRITER;

    private int mLoggingState;
    private static final int LOGGING_STATE_UNSTARTED = 0;
    private static final int LOGGING_STATE_RUNNING = 1;
    private static final int LOGGING_STATE_STOPPING = 2;
    private static final int LOGGING_STATE_STOPPED = 3;
    private static final int LOGGING_STATE_READY = 1;   // don't create file until necessary
    private static final int LOGGING_STATE_RUNNING = 2;
    private static final int LOGGING_STATE_STOPPING = 3;
    private static final int LOGGING_STATE_STOPPED = 4;
    private static final long FLUSH_DELAY_IN_MS = 1000 * 5;

    private static class NullOutputStream extends OutputStream {
@@ -94,11 +95,9 @@ public class ResearchLog {
    public synchronized void start() throws IOException {
        switch (mLoggingState) {
            case LOGGING_STATE_UNSTARTED:
                mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile)));
                mJsonWriter.setLenient(true);
                mJsonWriter.beginArray();
                mLoggingState = LOGGING_STATE_RUNNING;
                mLoggingState = LOGGING_STATE_READY;
                break;
            case LOGGING_STATE_READY:
            case LOGGING_STATE_RUNNING:
            case LOGGING_STATE_STOPPING:
            case LOGGING_STATE_STOPPED:
@@ -111,6 +110,7 @@ public class ResearchLog {
            case LOGGING_STATE_UNSTARTED:
                mLoggingState = LOGGING_STATE_STOPPED;
                break;
            case LOGGING_STATE_READY:
            case LOGGING_STATE_RUNNING:
                mExecutor.submit(new Callable<Object>() {
                    @Override
@@ -120,14 +120,13 @@ public class ResearchLog {
                            mJsonWriter.flush();
                            mJsonWriter.close();
                        } finally {
                            // the contentprovider only exports data if the writable
                            // bit is cleared.
                            boolean success = mFile.setWritable(false, false);
                            mLoggingState = LOGGING_STATE_STOPPED;
                        }
                        return null;
                    }
                });
                removeAnyScheduledFlush();
                mExecutor.shutdown();
                mLoggingState = LOGGING_STATE_STOPPING;
                break;
@@ -139,27 +138,26 @@ public class ResearchLog {
    public boolean isAlive() {
        switch (mLoggingState) {
            case LOGGING_STATE_UNSTARTED:
            case LOGGING_STATE_READY:
            case LOGGING_STATE_RUNNING:
                return true;
        }
        return false;
    }

    public void waitUntilStopped(int timeoutInMs) throws InterruptedException {
    public void waitUntilStopped(final int timeoutInMs) throws InterruptedException {
        removeAnyScheduledFlush();
        mExecutor.shutdown();
        mExecutor.awaitTermination(timeoutInMs, TimeUnit.MILLISECONDS);
    }

    private boolean isAbortSuccessful;
    public boolean isAbortSuccessful() {
        return isAbortSuccessful;
    }

    public synchronized void abort() {
        switch (mLoggingState) {
            case LOGGING_STATE_UNSTARTED:
                mLoggingState = LOGGING_STATE_STOPPED;
                isAbortSuccessful = true;
                break;
            case LOGGING_STATE_READY:
            case LOGGING_STATE_RUNNING:
                mExecutor.submit(new Callable<Object>() {
                    @Override
@@ -173,6 +171,7 @@ public class ResearchLog {
                        return null;
                    }
                });
                removeAnyScheduledFlush();
                mExecutor.shutdown();
                mLoggingState = LOGGING_STATE_STOPPING;
                break;
@@ -181,10 +180,16 @@ public class ResearchLog {
        }
    }

    private boolean isAbortSuccessful;
    public boolean isAbortSuccessful() {
        return isAbortSuccessful;
    }

    /* package */ synchronized void flush() {
        switch (mLoggingState) {
            case LOGGING_STATE_UNSTARTED:
                break;
            case LOGGING_STATE_READY:
            case LOGGING_STATE_RUNNING:
                removeAnyScheduledFlush();
                mExecutor.submit(mFlushCallable);
@@ -197,7 +202,9 @@ public class ResearchLog {
    private Callable<Object> mFlushCallable = new Callable<Object>() {
        @Override
        public Object call() throws Exception {
            if (mLoggingState == LOGGING_STATE_RUNNING) {
                mJsonWriter.flush();
            }
            return null;
        }
    };
@@ -220,6 +227,7 @@ public class ResearchLog {
        switch (mLoggingState) {
            case LOGGING_STATE_UNSTARTED:
                break;
            case LOGGING_STATE_READY:
            case LOGGING_STATE_RUNNING:
                mExecutor.submit(new Callable<Object>() {
                    @Override
@@ -239,6 +247,7 @@ public class ResearchLog {
        switch (mLoggingState) {
            case LOGGING_STATE_UNSTARTED:
                break;
            case LOGGING_STATE_READY:
            case LOGGING_STATE_RUNNING:
                mExecutor.submit(new Callable<Object>() {
                    @Override
@@ -260,6 +269,11 @@ public class ResearchLog {
    void outputEvent(final String[] keys, final Object[] values) {
        // not thread safe.
        try {
            if (mJsonWriter == NULL_JSON_WRITER) {
                mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile)));
                mJsonWriter.setLenient(true);
                mJsonWriter.beginArray();
            }
            mJsonWriter.beginObject();
            mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
            mJsonWriter.name(UPTIME_KEY).value(SystemClock.uptimeMillis());
+160 −83
Original line number Diff line number Diff line
@@ -44,7 +44,6 @@ import com.android.inputmethod.latin.RichInputConnection.Range;
import com.android.inputmethod.latin.define.ProductionFlag;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -64,13 +63,15 @@ import java.util.UUID;
public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
    private static final String TAG = ResearchLogger.class.getSimpleName();
    private static final boolean OUTPUT_ENTIRE_BUFFER = false;  // true may disclose private info
    /* package */ static final boolean DEFAULT_USABILITY_STUDY_MODE = false;
    /* package */ static boolean sIsLogging = false;
    private static final int OUTPUT_FORMAT_VERSION = 1;
    private static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
    private static final String FILENAME_PREFIX = "researchLog";
    /* package */ static final String FILENAME_PREFIX = "researchLog";
    private static final String FILENAME_SUFFIX = ".txt";
    private static final SimpleDateFormat TIMESTAMP_DATEFORMAT =
            new SimpleDateFormat("yyyyMMddHHmmssS", Locale.US);
    private static final boolean IS_SHOWING_INDICATOR = false;

    // constants related to specific log points
    private static final String WHITESPACE_SEPARATORS = " \t\n\r";
@@ -92,6 +93,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang

    private boolean mIsPasswordView = false;
    private boolean mIsLoggingSuspended = false;
    private SharedPreferences mPrefs;

    // digits entered by the user are replaced with this codepoint.
    /* package for test */ static final int DIGIT_REPLACEMENT_CODEPOINT =
@@ -101,6 +103,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
    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 = 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
    private static boolean sLatinIMEExpectingUpdateSelection = false;
@@ -124,7 +127,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
        if (ims == null) {
            Log.w(TAG, "IMS is null; logging is off");
        } else {
            mContext = ims;
            mFilesDir = ims.getFilesDir();
            if (mFilesDir == null || !mFilesDir.exists()) {
                Log.w(TAG, "IME storage directory does not exist.");
@@ -132,6 +134,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
        }
        if (prefs != null) {
            mUUIDString = getUUID(prefs);
            if (!prefs.contains(PREF_USABILITY_STUDY_MODE)) {
                Editor e = prefs.edit();
                e.putBoolean(PREF_USABILITY_STUDY_MODE, DEFAULT_USABILITY_STUDY_MODE);
                e.apply();
            }
            sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false);
            prefs.registerOnSharedPreferenceChangeListener(this);

@@ -146,6 +153,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
            }
        }
        mKeyboardSwitcher = keyboardSwitcher;
        mContext = ims;
        mPrefs = prefs;

        // TODO: force user to decide at splash screen instead of defaulting to on.
        setLoggingAllowed(true);
    }

    private void cleanupLoggingDir(final File dir, final long time) {
@@ -166,8 +178,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
        return new File(filesDir, sb.toString());
    }

    public void start() {
        if (!sIsLogging) {
    private void start() {
        updateSuspendedState();
        requestIndicatorRedraw();
        if (!isAllowedToLog()) {
            // Log.w(TAG, "not in usability mode; not logging");
            return;
        }
@@ -175,10 +189,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
            Log.w(TAG, "IME storage directory does not exist.  Cannot start logging.");
            return;
        }
        try {
            if (mMainResearchLog == null || !mMainResearchLog.isAlive()) {
                mMainResearchLog = new ResearchLog(createLogFile(mFilesDir));
            }
        try {
            mMainResearchLog.start();
            if (mIntentionalResearchLog == null || !mIntentionalResearchLog.isAlive()) {
                mIntentionalResearchLog = new ResearchLog(createLogFile(mFilesDir));
@@ -189,15 +203,26 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
        }
    }

    public void stop() {
    /* package */ void stop() {
        if (mMainResearchLog != null) {
            mMainResearchLog.stop();
        }
        if (mIntentionalResearchLog != null) {
            mIntentionalResearchLog.stop();
        }
    }

    private void setLoggingAllowed(boolean enableLogging) {
        if (mPrefs == null) {
            return;
        }
        Editor e = mPrefs.edit();
        e.putBoolean(PREF_USABILITY_STUDY_MODE, enableLogging);
        e.apply();
        sIsLogging = enableLogging;
    }

    public boolean abort() {
        mIsLoggingSuspended = true;
        requestIndicatorRedraw();
        boolean didAbortMainLog = false;
        if (mMainResearchLog != null) {
            mMainResearchLog.abort();
@@ -209,6 +234,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
            if (mMainResearchLog.isAbortSuccessful()) {
                didAbortMainLog = true;
            }
            mMainResearchLog = null;
        }
        boolean didAbortIntentionalLog = false;
        if (mIntentionalResearchLog != null) {
@@ -221,6 +247,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
            if (mIntentionalResearchLog.isAbortSuccessful()) {
                didAbortIntentionalLog = true;
            }
            mIntentionalResearchLog = null;
        }
        return didAbortMainLog && didAbortIntentionalLog;
    }
@@ -247,6 +274,34 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
        }
    }

    private void restart() {
        stop();
        start();
    }

    private long mResumeTime = 0L;
    private void suspendLoggingUntil(long time) {
        mIsLoggingSuspended = true;
        mResumeTime = time;
        requestIndicatorRedraw();
    }

    private void resumeLogging() {
        mResumeTime = 0L;
        updateSuspendedState();
        requestIndicatorRedraw();
        if (isAllowedToLog()) {
            restart();
        }
    }

    private void updateSuspendedState() {
        final long time = System.currentTimeMillis();
        if (time > mResumeTime) {
            mIsLoggingSuspended = false;
        }
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
        if (key == null || prefs == null) {
@@ -256,13 +311,15 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
        if (sIsLogging == false) {
            abort();
        }
        requestIndicatorRedraw();
    }

    /* package */ void presentResearchDialog(final LatinIME latinIME) {
        final CharSequence title = latinIME.getString(R.string.english_ime_research_log);
        final boolean showEnable = mIsLoggingSuspended || !sIsLogging;
        final CharSequence[] items = new CharSequence[] {
                latinIME.getString(R.string.note_timestamp_for_researchlog),
                mIsLoggingSuspended ? latinIME.getString(R.string.enable_session_logging) :
                showEnable ? latinIME.getString(R.string.enable_session_logging) :
                        latinIME.getString(R.string.do_not_log_this_session),
                latinIME.getString(R.string.log_whole_session_history),
        };
@@ -277,25 +334,25 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
                                Toast.LENGTH_LONG).show();
                        break;
                    case 1:
                        if (mIsLoggingSuspended) {
                            mIsLoggingSuspended = false;
                            requestIndicatorRedraw();
                            Toast toast = Toast.makeText(latinIME,
                                    R.string.notify_session_logging_enabled, Toast.LENGTH_LONG);
                        if (showEnable) {
                            if (!sIsLogging) {
                                setLoggingAllowed(true);
                            }
                            resumeLogging();
                            Toast.makeText(latinIME, R.string.notify_session_logging_enabled,
                                    Toast.LENGTH_LONG).show();
                        } else {
                            Toast toast = Toast.makeText(latinIME,
                                    R.string.notify_session_log_deleting, Toast.LENGTH_LONG);
                            toast.show();
                            boolean isLogDeleted = abort();
                            final long currentTime = System.currentTimeMillis();
                            final long resumeTime = currentTime + 1000 * 60 *
                                    SUSPEND_DURATION_IN_MINUTES;
                            suspendLoggingUntil(resumeTime);
                            toast.cancel();
                            if (isLogDeleted) {
                                Toast.makeText(latinIME, R.string.notify_session_log_deleted,
                            Toast.makeText(latinIME, R.string.notify_logging_suspended,
                                    Toast.LENGTH_LONG).show();
                            } else {
                                Toast.makeText(latinIME,
                                        R.string.notify_session_log_not_deleted, Toast.LENGTH_LONG)
                                        .show();
                            }
                        }
                        break;
                    case 2:
@@ -328,15 +385,17 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
    }

    private boolean isAllowedToLog() {
        return !mIsPasswordView && !mIsLoggingSuspended;
        return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging;
    }

    public void requestIndicatorRedraw() {
        // invalidate any existing graphics
        if (IS_SHOWING_INDICATOR) {
            if (mKeyboardSwitcher != null) {
                mKeyboardSwitcher.getKeyboardView().invalidateAllKeys();
            }
        }
    }

    private static final String CURRENT_TIME_KEY = "_ct";
    private static final String UPTIME_KEY = "_ut";
@@ -467,6 +526,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
    }

    private void publishLogUnit(LogUnit logUnit, boolean isPrivacySensitive) {
        if (!isAllowedToLog()) {
            return;
        }
        if (mMainResearchLog == null) {
            return;
        }
        if (isPrivacySensitive) {
            mMainResearchLog.publishPublicEvents(logUnit);
        } else {
@@ -536,6 +601,18 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
        }
    }

    private static String getUUID(final SharedPreferences prefs) {
        String uuidString = prefs.getString(PREF_RESEARCH_LOGGER_UUID_STRING, null);
        if (null == uuidString) {
            UUID uuid = UUID.randomUUID();
            uuidString = uuid.toString();
            Editor editor = prefs.edit();
            editor.putString(PREF_RESEARCH_LOGGER_UUID_STRING, uuidString);
            editor.apply();
        }
        return uuidString;
    }

    private String scrubWord(String word) {
        if (mDictionary == null) {
            return WORD_REPLACEMENT_STRING;
@@ -546,9 +623,62 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
        return WORD_REPLACEMENT_STRING;
    }

    // Special methods related to startup, shutdown, logging itself

    private static final String[] EVENTKEYS_INTENTIONAL_LOG = {
        "IntentionalLog"
    };

    private static final String[] EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL = {
        "LatinIMEOnStartInputViewInternal", "uuid", "packageName", "inputType", "imeOptions",
        "fieldId", "display", "model", "prefs", "versionCode", "versionName", "outputFormatVersion"
    };
    public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
            final SharedPreferences prefs) {
        final ResearchLogger researchLogger = getInstance();
        researchLogger.start();
        if (editorInfo != null) {
            final Context context = researchLogger.mContext;
            try {
                final PackageInfo packageInfo;
                packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(),
                        0);
                final Integer versionCode = packageInfo.versionCode;
                final String versionName = packageInfo.versionName;
                final Object[] values = {
                        researchLogger.mUUIDString, editorInfo.packageName,
                        Integer.toHexString(editorInfo.inputType),
                        Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId,
                        Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName,
                        OUTPUT_FORMAT_VERSION
                };
                researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL, values);
            } catch (NameNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    public void latinIME_onFinishInputInternal() {
        stop();
    }

    private static final String[] EVENTKEYS_LATINIME_COMMITTEXT = {
        "LatinIMECommitText", "typedWord"
    };

    public static void latinIME_commitText(final CharSequence typedWord) {
        final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
        final Object[] values = {
            scrubbedWord
        };
        final ResearchLogger researchLogger = getInstance();
        researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_COMMITTEXT, values);
        researchLogger.onWordComplete(scrubbedWord);
    }

    // Regular logging methods

    private static final String[] EVENTKEYS_LATINKEYBOARDVIEW_PROCESSMOTIONEVENT = {
        "LatinKeyboardViewProcessMotionEvent", "action", "eventTime", "id", "x", "y", "size",
        "pressure"
@@ -611,19 +741,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
                EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION, values);
    }

    private static final String[] EVENTKEYS_LATINIME_COMMITTEXT = {
        "LatinIMECommitText", "typedWord"
    };
    public static void latinIME_commitText(final CharSequence typedWord) {
        final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
        final Object[] values = {
            scrubbedWord
        };
        final ResearchLogger researchLogger = getInstance();
        researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_COMMITTEXT, values);
        researchLogger.onWordComplete(scrubbedWord);
    }

    private static final String[] EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT = {
        "LatinIMEDeleteSurroundingText", "length"
    };
@@ -702,51 +819,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
            // Play it safe.  Remove privacy-sensitive events.
            researchLogger.publishLogUnit(researchLogger.mCurrentLogUnit, true);
            researchLogger.mCurrentLogUnit = new LogUnit();
            getInstance().restart();
        }
    }

    private static final String[] EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL = {
        "LatinIMEOnStartInputViewInternal", "uuid", "packageName", "inputType", "imeOptions",
        "fieldId", "display", "model", "prefs", "versionCode", "versionName", "outputFormatVersion"
    };
    public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
            final SharedPreferences prefs) {
        final ResearchLogger researchLogger = getInstance();
        researchLogger.start();
        if (editorInfo != null) {
            final Context context = researchLogger.mContext;
            try {
                final PackageInfo packageInfo;
                packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(),
                        0);
                final Integer versionCode = packageInfo.versionCode;
                final String versionName = packageInfo.versionName;
                final Object[] values = {
                        researchLogger.mUUIDString, editorInfo.packageName,
                        Integer.toHexString(editorInfo.inputType),
                        Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId,
                        Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName,
                        OUTPUT_FORMAT_VERSION
                };
                researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL, values);
            } catch (NameNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    private static String getUUID(final SharedPreferences prefs) {
        String uuidString = prefs.getString(PREF_RESEARCH_LOGGER_UUID_STRING, null);
        if (null == uuidString) {
            UUID uuid = UUID.randomUUID();
            uuidString = uuid.toString();
            Editor editor = prefs.edit();
            editor.putString(PREF_RESEARCH_LOGGER_UUID_STRING, uuidString);
            editor.apply();
        }
        return uuidString;
    }

    private static final String[] EVENTKEYS_LATINIME_ONUPDATESELECTION = {
        "LatinIMEOnUpdateSelection", "lastSelectionStart", "lastSelectionEnd", "oldSelStart",
        "oldSelEnd", "newSelStart", "newSelEnd", "composingSpanStart", "composingSpanEnd",
@@ -873,6 +949,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
        if (keyboard != null) {
            final KeyboardId kid = keyboard.mId;
            final boolean isPasswordView = kid.passwordInput();
            getInstance().setIsPasswordView(isPasswordView);
            final Object[] values = {
                    KeyboardId.elementIdToName(kid.mElementId),
                    kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
+2 −1

File changed.

Preview size limit exceeded, changes collapsed.