Merge "Import translations. DO NOT MERGE"
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 252fb02..e7c64c6 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1451,7 +1451,7 @@
         } else {
             didAutoCorrect = false;
             if (SPACE_STATE_PHANTOM == spaceState) {
-                if (ProductionFlag.IS_INTERNAL) {
+                if (mSettings.isInternal()) {
                     if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
                         Stats.onAutoCorrection(
                                 "", mWordComposer.getTypedWord(), " ", mWordComposer);
@@ -1506,7 +1506,7 @@
         mHandler.cancelUpdateSuggestionStrip();
         mConnection.beginBatchEdit();
         if (mWordComposer.isComposingWord()) {
-            if (ProductionFlag.IS_INTERNAL) {
+            if (mSettings.isInternal()) {
                 if (mWordComposer.isBatchMode()) {
                     Stats.onAutoCorrection("", mWordComposer.getTypedWord(), " ", mWordComposer);
                 }
@@ -1736,7 +1736,7 @@
             }
         } else {
             if (mLastComposedWord.canRevertCommit()) {
-                if (ProductionFlag.IS_INTERNAL) {
+                if (mSettings.isInternal()) {
                     Stats.onAutoCorrectionCancellation();
                 }
                 revertCommit();
@@ -1895,7 +1895,7 @@
             if (null != mSuggestionStripView) mSuggestionStripView.dismissAddToDictionaryHint();
         }
         mHandler.postUpdateSuggestionStrip();
-        if (ProductionFlag.IS_INTERNAL) {
+        if (mSettings.isInternal()) {
             Utils.Stats.onNonSeparator((char)primaryCode, x, y);
         }
     }
@@ -1962,7 +1962,7 @@
             // already displayed or not, so it's okay.
             setPunctuationSuggestions();
         }
-        if (ProductionFlag.IS_INTERNAL) {
+        if (mSettings.isInternal()) {
             Utils.Stats.onSeparator((char)primaryCode, x, y);
         }
 
@@ -2145,7 +2145,7 @@
                 throw new RuntimeException("We have an auto-correction but the typed word "
                         + "is empty? Impossible! I must commit suicide.");
             }
-            if (ProductionFlag.IS_INTERNAL) {
+            if (mSettings.isInternal()) {
                 Stats.onAutoCorrection(typedWord, autoCorrection, separatorString, mWordComposer);
             }
             if (ProductionFlag.IS_EXPERIMENTAL) {
@@ -2244,7 +2244,7 @@
                 // If the suggestion is not in the dictionary, the hint should be shown.
                 && !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true);
 
-        if (ProductionFlag.IS_INTERNAL) {
+        if (mSettings.isInternal()) {
             Stats.onSeparator((char)Constants.CODE_SPACE,
                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
         }
@@ -2368,7 +2368,7 @@
             mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
         }
         mConnection.commitText(originallyTypedWord + mLastComposedWord.mSeparatorString, 1);
-        if (ProductionFlag.IS_INTERNAL) {
+        if (mSettings.isInternal()) {
             Stats.onSeparator(mLastComposedWord.mSeparatorString,
                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
         }
@@ -2502,12 +2502,6 @@
         launchSubActivity(SettingsActivity.class);
     }
 
-    // Called from debug code only
-    public void launchDebugSettings() {
-        handleClose();
-        launchSubActivity(DebugSettingsActivity.class);
-    }
-
     public void launchKeyboardedDialogActivity(final Class<? extends Activity> activityClass) {
         // Put the text in the attached EditText into a safe, saved state before switching to a
         // new activity that will also use the soft keyboard.
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 435074b..4cbfa8e 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -70,6 +70,7 @@
     public static final String PREF_INPUT_LANGUAGE = "input_language";
     public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
     public static final String PREF_DEBUG_SETTINGS = "debug_settings";
+    public static final String PREF_KEY_IS_INTERNAL = "pref_key_is_internal";
 
     // This preference key is deprecated. Use {@link #PREF_SHOW_LANGUAGE_SWITCH_KEY} instead.
     // This is being used only for the backward compatibility.
@@ -127,6 +128,10 @@
         return mSettingsValues;
     }
 
+    public boolean isInternal() {
+        return mSettingsValues.mIsInternal;
+    }
+
     // Accessed from the settings interface, hence public
     public static boolean readKeypressSoundEnabled(final SharedPreferences prefs,
             final Resources res) {
@@ -274,4 +279,8 @@
         }
         return prefs.getBoolean(Settings.PREF_SHOW_SETUP_WIZARD_ICON, false);
     }
+
+    public static boolean isInternal(final SharedPreferences prefs) {
+        return prefs.getBoolean(Settings.PREF_KEY_IS_INTERNAL, false);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index 4c90e48..cc9f168 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -92,7 +92,7 @@
 
         final Preference debugSettings = findPreference(Settings.PREF_DEBUG_SETTINGS);
         if (debugSettings != null) {
-            if (ProductionFlag.IS_INTERNAL) {
+            if (Settings.isInternal(prefs)) {
                 final Intent debugSettingsIntent = new Intent(Intent.ACTION_MAIN);
                 debugSettingsIntent.setClassName(
                         context.getPackageName(), DebugSettingsActivity.class.getName());
@@ -156,10 +156,6 @@
             removePreference(Settings.PREF_GESTURE_SETTINGS, getPreferenceScreen());
         }
 
-        final CheckBoxPreference showSetupWizardIcon =
-                (CheckBoxPreference)findPreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON);
-        showSetupWizardIcon.setChecked(Settings.readShowSetupWizardIcon(prefs, context));
-
         setupKeyLongpressTimeoutSettings(prefs, res);
         setupKeypressVibrationDurationSettings(prefs, res);
         setupKeypressSoundVolumeSettings(prefs, res);
@@ -175,6 +171,10 @@
         } else {
             getPreferenceScreen().removePreference(mVoicePreference);
         }
+        final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
+        final CheckBoxPreference showSetupWizardIcon =
+                (CheckBoxPreference)findPreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON);
+        showSetupWizardIcon.setChecked(Settings.readShowSetupWizardIcon(prefs, getActivity()));
         updateShowCorrectionSuggestionsSummary();
         updateKeyPreviewPopupDelaySummary();
         updateCustomInputStylesSummary();
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index d058680..f77a928 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -76,6 +76,9 @@
     private final boolean mVoiceKeyEnabled;
     private final boolean mVoiceKeyOnMain;
 
+    // Debug settings
+    public final boolean mIsInternal;
+
     public SettingsValues(final SharedPreferences prefs, final Resources res,
             final InputAttributes inputAttributes) {
         // Get the resources
@@ -141,6 +144,7 @@
                 Settings.PREF_SHOW_SUGGESTIONS_SETTING,
                 res.getString(R.string.prefs_suggestion_visibility_default_value));
         mSuggestionVisibility = createSuggestionVisibility(res, showSuggestionsSetting);
+        mIsInternal = Settings.isInternal(prefs);
     }
 
     public boolean isApplicationSpecifiedCompletionsOn() {
diff --git a/java/src/com/android/inputmethod/latin/StaticInnerHandlerWrapper.java b/java/src/com/android/inputmethod/latin/StaticInnerHandlerWrapper.java
index 89d9ea8..e50af4d 100644
--- a/java/src/com/android/inputmethod/latin/StaticInnerHandlerWrapper.java
+++ b/java/src/com/android/inputmethod/latin/StaticInnerHandlerWrapper.java
@@ -22,17 +22,17 @@
 import java.lang.ref.WeakReference;
 
 public class StaticInnerHandlerWrapper<T> extends Handler {
-    final private WeakReference<T> mOuterInstanceRef;
+    private final WeakReference<T> mOuterInstanceRef;
 
-    public StaticInnerHandlerWrapper(T outerInstance) {
-        super();
-        if (outerInstance == null) throw new NullPointerException("outerInstance is null");
-        mOuterInstanceRef = new WeakReference<T>(outerInstance);
+    public StaticInnerHandlerWrapper(final T outerInstance) {
+        this(outerInstance, Looper.myLooper());
     }
 
-    public StaticInnerHandlerWrapper(T outerInstance, Looper looper) {
+    public StaticInnerHandlerWrapper(final T outerInstance, final Looper looper) {
         super(looper);
-        if (outerInstance == null) throw new NullPointerException("outerInstance is null");
+        if (outerInstance == null) {
+            throw new NullPointerException("outerInstance is null");
+        }
         mOuterInstanceRef = new WeakReference<T>(outerInstance);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
index fe9be16..3df0d04 100644
--- a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
+++ b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
@@ -22,7 +22,6 @@
     }
 
     public static final boolean IS_EXPERIMENTAL = false;
-    public static final boolean IS_INTERNAL = false;
 
     // When false, IS_EXPERIMENTAL_DEBUG suggests that all guarded class-private DEBUG flags should
     // be false, and any privacy controls should be enforced.  IS_EXPERIMENTAL_DEBUG should be false
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
index e009fbc..7f66c6d 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
@@ -62,6 +62,9 @@
         @Override
         public void handleMessage(final Message msg) {
             final SetupActivity setupActivity = getOuterInstance();
+            if (setupActivity == null) {
+                return;
+            }
             switch (msg.what) {
             case MSG_POLLING_IME_SETTINGS:
                 if (SetupActivity.isThisImeEnabled(setupActivity)) {
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 9016e23..f7c5fd5 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.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;
@@ -54,7 +54,6 @@
     private static final String TAG = ResearchLog.class.getSimpleName();
     private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_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 +98,10 @@
      * 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 {
@@ -131,15 +132,24 @@
         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 +161,10 @@
                     }
                 } finally {
                     if (mFile != null) {
-                        mIsAbortSuccessful = mFile.delete();
+                        mFile.delete();
+                    }
+                    if (onAbort != null) {
+                        onAbort.run();
                     }
                 }
                 return null;
@@ -161,14 +174,25 @@
         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 +238,10 @@
                     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/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index e705ddd..11d1a52 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -154,6 +154,9 @@
     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 ResearchLogger sInstance = new ResearchLogger();
     private static String sAccountType = null;
     private static String sAllowedAccountDomain = null;
@@ -502,42 +505,29 @@
             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,7 +610,7 @@
 
     private void startRecordingInternal() {
         if (mUserRecordingLog != null) {
-            mUserRecordingLog.abort();
+            mUserRecordingLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS);
         }
         mUserRecordingFile = createUserRecordingFile(mFilesDir);
         mUserRecordingLog = new ResearchLog(mUserRecordingFile, mLatinIME);
@@ -658,7 +648,7 @@
 
     private void cancelRecording() {
         if (mUserRecordingLog != null) {
-            mUserRecordingLog.abort();
+            mUserRecordingLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS);
         }
         mUserRecordingLog = null;
         mUserRecordingLogBuffer = null;
@@ -670,7 +660,7 @@
     private void saveRecording() {
         commitCurrentLogUnit();
         publishLogBuffer(mUserRecordingLogBuffer, mUserRecordingLog, true);
-        mUserRecordingLog.close(null);
+        mUserRecordingLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
         mUserRecordingLog = null;
         mUserRecordingLogBuffer = null;
 
@@ -782,12 +772,8 @@
                 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();
diff --git a/native/jni/src/char_utils.h b/native/jni/src/char_utils.h
index 7a4384d..58d388d 100644
--- a/native/jni/src/char_utils.h
+++ b/native/jni/src/char_utils.h
@@ -68,7 +68,7 @@
     return toLowerCase(toBaseCodePoint(c));
 }
 
-inline static bool isSkippableCodePoint(const int codePoint) {
+inline static bool isIntentionalOmissionCodePoint(const int codePoint) {
     // TODO: Do not hardcode here
     return codePoint == KEYCODE_SINGLE_QUOTE || codePoint == KEYCODE_HYPHEN_MINUS;
 }
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
index fe1c433..7fcfd5d 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/proximity_info_state.cpp
@@ -165,7 +165,7 @@
         const int index = inputIndex * mProximityInfo->getKeyCount() + keyId;
         return min(mSampledDistanceCache_G[index], mMaxPointToKeyLength);
     }
-    if (isSkippableCodePoint(codePoint)) {
+    if (isIntentionalOmissionCodePoint(codePoint)) {
         return 0.0f;
     }
     // If the char is not a key on the keyboard then return the max length.