Merge "Change a heuristic and add a log."
diff --git a/java/res/values/strings-config-important-notice.xml b/java/res/values/strings-config-important-notice.xml
index 3be95d3..f2229be 100644
--- a/java/res/values/strings-config-important-notice.xml
+++ b/java/res/values/strings-config-important-notice.xml
@@ -20,11 +20,14 @@
 
 <resources>
     <integer name="config_important_notice_version">0</integer>
-    <!-- TODO: Make title and contents resource to string array indexed by version. -->
-    <!-- The text of the important notice displayed on the suggestion strip. -->
-    <string name="important_notice_title"></string>
-    <!-- The contents of the important notice. -->
-    <string name="important_notice_contents"></string>
+    <!-- The array of the text of the important notices displayed on the suggestion strip. -->
+    <string-array name="important_notice_title_array">
+        <!-- empty -->
+    </string-array>
+    <!-- The array of the contents of the important notices. -->
+    <string-array name="important_notice_contents_array">
+        <!-- empty -->
+    </string-array>
     <!-- Description for option enabling the use by the keyboards of sent/received messages, e-mail and typing history to improve suggestion accuracy [CHAR LIMIT=68] -->
     <string name="use_personalized_dicts_summary">Learn from your communications and typed data to improve suggestions</string>
 </resources>
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
index 93d80c9..96acb15 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
@@ -24,7 +24,8 @@
  * !!!!! DO NOT EDIT THIS FILE !!!!!
  *
  * This file is generated by tools/make-keyboard-text. The base template file is
- *   tools/make-keyboard-text/res/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.tmpl
+ *   tools/make-keyboard-text/res/src/com/android/inputmethod/keyboard/internal/
+ *   KeyboardTextsTable.tmpl
  *
  * This file must be updated when any text resources in keyboard layout files have been changed.
  * These text resources are referred as "!text/<resource_name>" in keyboard XML definitions,
@@ -34,7 +35,7 @@
  * To update this file, please run the following commands.
  *   $ cd $ANDROID_BUILD_TOP
  *   $ mmm packages/inputmethods/LatinIME/tools/make-keyboard-text
- *   $ make-keyboard-text -java packages/inputmethods/LatinIME/java/src
+ *   $ make-keyboard-text -java packages/inputmethods/LatinIME/java
  *
  * The updated source file will be generated to the following path (this file).
  *   packages/inputmethods/LatinIME/java/src/com/android/inputmethod/keyboard/internal/
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 013f922..c450a1d 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.latin;
 
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.inputmethod.annotations.UsedForTesting;
@@ -29,6 +30,7 @@
 import com.android.inputmethod.latin.makedict.WordProperty;
 import com.android.inputmethod.latin.settings.NativeSuggestOptions;
 import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.FileUtils;
 import com.android.inputmethod.latin.utils.JniUtils;
 import com.android.inputmethod.latin.utils.LanguageModelParam;
 import com.android.inputmethod.latin.utils.StringUtils;
@@ -84,6 +86,7 @@
     private final Locale mLocale;
     private final long mDictSize;
     private final String mDictFilePath;
+    private final boolean mIsUpdatable;
     private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH];
     private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS];
     private final int[] mSpaceIndices = new int[MAX_RESULTS];
@@ -130,6 +133,7 @@
         mLocale = locale;
         mDictSize = length;
         mDictFilePath = filename;
+        mIsUpdatable = isUpdatable;
         mNativeSuggestOptions.setUseFullEditDistance(useFullEditDistance);
         loadDictionary(filename, offset, length, isUpdatable);
     }
@@ -177,6 +181,7 @@
             int bigramProbability);
     private static native int setCurrentTimeForTestNative(int currentTime);
     private static native String getPropertyNative(long dict, String query);
+    private static native boolean isCorruptedNative(long dict);
 
     public static boolean createEmptyDictFile(final String filePath, final long dictVersion,
             final Locale locale, final Map<String, String> attributeMap) {
@@ -198,6 +203,22 @@
         mNativeDict = openNative(path, startOffset, length, isUpdatable);
     }
 
+    // TODO: Check isCorrupted() for main dictionaries.
+    public boolean isCorrupted() {
+        if (!isValidDictionary()) {
+            return false;
+        }
+        if (!isCorruptedNative(mNativeDict)) {
+            return false;
+        }
+        // TODO: Record the corruption.
+        Log.e(TAG, "BinaryDictionary (" + mDictFilePath + ") is corrupted.");
+        Log.e(TAG, "locale: " + mLocale);
+        Log.e(TAG, "dict size: " + mDictSize);
+        Log.e(TAG, "updatable: " + mIsUpdatable);
+        return true;
+    }
+
     @UsedForTesting
     public DictionaryHeader getHeader() throws UnsupportedFormatException {
         if (mNativeDict == 0) {
@@ -444,7 +465,7 @@
         // only be called for actual files. Right now it's only called by the flush() family of
         // functions, which require an updatable dictionary, so it's okay. But beware.
         loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */,
-                dictFile.length(), true /* isUpdatable */);
+                dictFile.length(), mIsUpdatable);
     }
 
     public void flush() {
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 230739d..f9ab941 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -276,22 +276,26 @@
         return attributeMap;
     }
 
+    private void removeBinaryDictionaryLocked() {
+        if (mBinaryDictionary != null) {
+            mBinaryDictionary.close();
+        }
+        if (mDictFile.exists() && !FileUtils.deleteRecursively(mDictFile)) {
+            Log.e(TAG, "Can't remove a file: " + mDictFile.getName());
+        }
+        mBinaryDictionary = null;
+    }
+
     protected void clear() {
-        final File dictFile = mDictFile;
         getExecutor(mDictName).execute(new Runnable() {
             @Override
             public void run() {
                 if (mDictionaryWriter == null) {
-                    if (mBinaryDictionary != null) {
-                        mBinaryDictionary.close();
-                    }
-                    if (dictFile.exists() && !FileUtils.deleteRecursively(dictFile)) {
-                        Log.e(TAG, "Can't remove a file: " + dictFile.getName());
-                    }
-                    BinaryDictionary.createEmptyDictFile(dictFile.getAbsolutePath(),
+                    removeBinaryDictionaryLocked();
+                    BinaryDictionary.createEmptyDictFile(mDictFile.getAbsolutePath(),
                             DICTIONARY_FORMAT_VERSION, mLocale, getHeaderAttributeMap());
                     mBinaryDictionary = new BinaryDictionary(
-                            dictFile.getAbsolutePath(), 0 /* offset */, dictFile.length(),
+                            mDictFile.getAbsolutePath(), 0 /* offset */, mDictFile.length(),
                             true /* useFullEditDistance */, mLocale, mDictType, mIsUpdatable);
                 } else {
                     mDictionaryWriter.clear();
@@ -469,6 +473,9 @@
                                 proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
                                 sessionId);
                 holder.set(binarySuggestion);
+                if (mBinaryDictionary.isCorrupted()) {
+                    removeBinaryDictionaryLocked();
+                }
             }
         });
         return holder.get(null, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
diff --git a/java/src/com/android/inputmethod/latin/ImportantNoticeDialog.java b/java/src/com/android/inputmethod/latin/ImportantNoticeDialog.java
new file mode 100644
index 0000000..9870faa
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ImportantNoticeDialog.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 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.latin;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.DialogInterface.OnShowListener;
+
+import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
+
+/**
+ * The dialog box that shows the important notice contents.
+ */
+public final class ImportantNoticeDialog extends AlertDialog implements OnShowListener,
+        OnClickListener, OnDismissListener {
+    public interface ImportantNoticeDialogListener {
+        public void onClickSettingsOfImportantNoticeDialog(final int nextVersion);
+        public void onDismissImportantNoticeDialog(final int nextVersion);
+    }
+
+    private final ImportantNoticeDialogListener mListener;
+    private final int mNextImportantNoticeVersion;
+
+    public ImportantNoticeDialog(
+            final Context context, final ImportantNoticeDialogListener listener) {
+        super(context, THEME_HOLO_DARK);
+        mListener = listener;
+        mNextImportantNoticeVersion = ImportantNoticeUtils.getNextImportantNoticeVersion(context);
+        setMessage(ImportantNoticeUtils.getNextImportantNoticeContents(context));
+        // Create buttons and set listeners.
+        setButton(BUTTON_POSITIVE, context.getString(android.R.string.ok), this);
+        if (shouldHaveSettingsButton()) {
+            setButton(BUTTON_NEGATIVE, context.getString(R.string.go_to_settings), this);
+        }
+        // Set listeners.
+        setOnShowListener(this);
+        setOnDismissListener(this);
+    }
+
+    private boolean shouldHaveSettingsButton() {
+        return mNextImportantNoticeVersion
+                == ImportantNoticeUtils.VERSION_TO_ENABLE_PERSONALIZED_SUGGESTIONS;
+    }
+
+    @Override
+    public void onShow(final DialogInterface dialog) {
+        ImportantNoticeUtils.updateLastImportantNoticeVersion(getContext());
+    }
+
+    @Override
+    public void onClick(final DialogInterface dialog, final int which) {
+        if (shouldHaveSettingsButton() && which == BUTTON_NEGATIVE) {
+            mListener.onClickSettingsOfImportantNoticeDialog(mNextImportantNoticeVersion);
+        }
+    }
+
+    @Override
+    public void onDismiss(final DialogInterface dialog) {
+        mListener.onDismissImportantNoticeDialog(mNextImportantNoticeVersion);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 16d5f5b..44282a4 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -26,8 +26,6 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
-import android.content.DialogInterface.OnDismissListener;
-import android.content.DialogInterface.OnShowListener;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
@@ -100,7 +98,8 @@
  */
 public class LatinIME extends InputMethodService implements KeyboardActionListener,
         SuggestionStripView.Listener, SuggestionStripViewAccessor,
-        DictionaryFacilitatorForSuggest.DictionaryInitializationListener {
+        DictionaryFacilitatorForSuggest.DictionaryInitializationListener,
+        ImportantNoticeDialog.ImportantNoticeDialogListener {
     private static final String TAG = LatinIME.class.getSimpleName();
     private static final boolean TRACE = false;
     private static boolean DEBUG = false;
@@ -1182,39 +1181,23 @@
         mInputLogic.mSuggest.mDictionaryFacilitator.addWordToUserDictionary(wordToEdit);
     }
 
-    // TODO: Move this method out of {@link LatinIME}.
     // Callback for the {@link SuggestionStripView}, to call when the important notice strip is
     // pressed.
     @Override
     public void showImportantNoticeContents() {
-        final Context context = this;
-        final AlertDialog.Builder builder =
-                new AlertDialog.Builder(context, AlertDialog.THEME_HOLO_DARK);
-        builder.setMessage(ImportantNoticeUtils.getNextImportantNoticeContents(context));
-        builder.setPositiveButton(android.R.string.ok, null /* listener */);
-        final OnClickListener onClickListener = new OnClickListener() {
-            @Override
-            public void onClick(final DialogInterface dialog, final int position) {
-                if (position == DialogInterface.BUTTON_NEGATIVE) {
-                    launchSettings();
-                }
-            }
-        };
-        builder.setNegativeButton(R.string.go_to_settings, onClickListener);
-        final AlertDialog importantNoticeDialog = builder.create();
-        importantNoticeDialog.setOnShowListener(new OnShowListener() {
-            @Override
-            public void onShow(final DialogInterface dialog) {
-                ImportantNoticeUtils.updateLastImportantNoticeVersion(context);
-            }
-        });
-        importantNoticeDialog.setOnDismissListener(new OnDismissListener() {
-            @Override
-            public void onDismiss(final DialogInterface dialog) {
-                setNeutralSuggestionStrip();
-            }
-        });
-        showOptionDialog(importantNoticeDialog);
+        showOptionDialog(new ImportantNoticeDialog(this /* context */, this /* listener */));
+    }
+
+    // Implement {@link ImportantNoticeDialog.ImportantNoticeDialogListener}
+    @Override
+    public void onClickSettingsOfImportantNoticeDialog(final int nextVersion) {
+        launchSettings();
+    }
+
+    // Implement {@link ImportantNoticeDialog.ImportantNoticeDialogListener}
+    @Override
+    public void onDismissImportantNoticeDialog(final int nextVersion) {
+        setNeutralSuggestionStrip();
     }
 
     public void displaySettingsDialog() {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index eeb5bf5..045d06f 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -490,6 +490,7 @@
         handler.showGesturePreviewAndSuggestionStrip(
                 SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */);
         handler.cancelUpdateSuggestionStrip();
+        ++mAutoCommitSequenceNumber;
         mConnection.beginBatchEdit();
         if (mWordComposer.isComposingWord()) {
             if (settingsValues.mIsInternal) {
@@ -587,6 +588,7 @@
     public void onEndBatchInput(final SettingsValues settingValues,
             final InputPointers batchPointers) {
         mInputLogicHandler.onEndBatchInput(batchPointers, mAutoCommitSequenceNumber);
+        ++mAutoCommitSequenceNumber;
     }
 
     // TODO: remove this argument
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java
index 71e120c..bf776cf 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java
@@ -120,16 +120,10 @@
     // used only for testing.
     private final DictionaryBufferFactory mBufferFactory;
     protected DictBuffer mDictBuffer;
-    private final BinaryDictionary mBinaryDictionary;
 
     /* package */ Ver2DictDecoder(final File file, final int factoryFlag) {
         mDictionaryBinaryFile = file;
         mDictBuffer = null;
-        // dictType is not being used in dicttool. Passing an empty string.
-        mBinaryDictionary = new BinaryDictionary(file.getAbsolutePath(),
-                0 /* offset */, file.length() /* length */, true /* useFullEditDistance */,
-                null /* locale */, "" /* dictType */, false /* isUpdatable */);
-
         if ((factoryFlag & MASK_DICTBUFFER) == USE_READONLY_BYTEBUFFER) {
             mBufferFactory = new DictionaryBufferFromReadOnlyByteBufferFactory();
         } else if ((factoryFlag  & MASK_DICTBUFFER) == USE_BYTEARRAY) {
@@ -144,10 +138,6 @@
     /* package */ Ver2DictDecoder(final File file, final DictionaryBufferFactory factory) {
         mDictionaryBinaryFile = file;
         mBufferFactory = factory;
-        // dictType is not being used in dicttool. Passing an empty string.
-        mBinaryDictionary = new BinaryDictionary(file.getAbsolutePath(),
-                0 /* offset */, file.length() /* length */, true /* useFullEditDistance */,
-                null /* locale */, "" /* dictType */, false /* isUpdatable */);
     }
 
     @Override
@@ -172,7 +162,13 @@
 
     @Override
     public DictionaryHeader readHeader() throws IOException, UnsupportedFormatException {
-        final DictionaryHeader header = mBinaryDictionary.getHeader();
+        // dictType is not being used in dicttool. Passing an empty string.
+        final BinaryDictionary binaryDictionary = new BinaryDictionary(
+                mDictionaryBinaryFile.getAbsolutePath(), 0 /* offset */,
+                mDictionaryBinaryFile.length() /* length */, true /* useFullEditDistance */,
+                null /* locale */, "" /* dictType */, false /* isUpdatable */);
+        final DictionaryHeader header = binaryDictionary.getHeader();
+        binaryDictionary.close();
         if (header == null) {
             throw new IOException("Cannot read the dictionary header.");
         }
@@ -254,6 +250,11 @@
     @Override
     public FusionDictionary readDictionaryBinary(final boolean deleteDictIfBroken)
             throws FileNotFoundException, IOException, UnsupportedFormatException {
+        // dictType is not being used in dicttool. Passing an empty string.
+        final BinaryDictionary binaryDictionary = new BinaryDictionary(
+                mDictionaryBinaryFile.getAbsolutePath(), 0 /* offset */,
+                mDictionaryBinaryFile.length() /* length */, true /* useFullEditDistance */,
+                null /* locale */, "" /* dictType */, false /* isUpdatable */);
         final DictionaryHeader header = readHeader();
         final FusionDictionary fusionDict =
                 new FusionDictionary(new FusionDictionary.PtNodeArray(), header.mDictionaryOptions);
@@ -261,11 +262,11 @@
         final ArrayList<WordProperty> wordProperties = CollectionUtils.newArrayList();
         do {
             final BinaryDictionary.GetNextWordPropertyResult result =
-                    mBinaryDictionary.getNextWordProperty(token);
+                    binaryDictionary.getNextWordProperty(token);
             final WordProperty wordProperty = result.mWordProperty;
             if (wordProperty == null) {
+                binaryDictionary.close();
                 if (deleteDictIfBroken) {
-                    mBinaryDictionary.close();
                     mDictionaryBinaryFile.delete();
                 }
                 return null;
@@ -294,6 +295,7 @@
                 fusionDict.setBigram(word0, bigram.mWord, bigram.mProbabilityInfo);
             }
         }
+        binaryDictionary.close();
         return fusionDict;
     }
 
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
index 88fff38..afe8231 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
@@ -35,7 +35,6 @@
     private static final String TAG = Ver4DictDecoder.class.getSimpleName();
 
     final File mDictDirectory;
-    final BinaryDictionary mBinaryDictionary;
 
     @UsedForTesting
     /* package */ Ver4DictDecoder(final File dictDirectory, final int factoryFlag) {
@@ -45,24 +44,32 @@
     @UsedForTesting
     /* package */ Ver4DictDecoder(final File dictDirectory, final DictionaryBufferFactory factory) {
         mDictDirectory = dictDirectory;
-        // dictType is not being used in dicttool. Passing an empty string.
-        mBinaryDictionary = new BinaryDictionary(dictDirectory.getAbsolutePath(),
-                0 /* offset */, 0 /* length */, true /* useFullEditDistance */, null /* locale */,
-                "" /* dictType */, true /* isUpdatable */);
+
     }
 
     @Override
     public DictionaryHeader readHeader() throws IOException, UnsupportedFormatException {
-        final DictionaryHeader header = mBinaryDictionary.getHeader();
+        // dictType is not being used in dicttool. Passing an empty string.
+        final BinaryDictionary binaryDictionary= new BinaryDictionary(
+              mDictDirectory.getAbsolutePath(), 0 /* offset */, 0 /* length */,
+              true /* useFullEditDistance */, null /* locale */,
+              "" /* dictType */, true /* isUpdatable */);
+        final DictionaryHeader header = binaryDictionary.getHeader();
+        binaryDictionary.close();
         if (header == null) {
             throw new IOException("Cannot read the dictionary header.");
         }
-        return mBinaryDictionary.getHeader();
+        return header;
     }
 
     @Override
     public FusionDictionary readDictionaryBinary(final boolean deleteDictIfBroken)
             throws FileNotFoundException, IOException, UnsupportedFormatException {
+        // dictType is not being used in dicttool. Passing an empty string.
+        final BinaryDictionary binaryDictionary = new BinaryDictionary(
+              mDictDirectory.getAbsolutePath(), 0 /* offset */, 0 /* length */,
+              true /* useFullEditDistance */, null /* locale */,
+              "" /* dictType */, true /* isUpdatable */);
         final DictionaryHeader header = readHeader();
         final FusionDictionary fusionDict =
                 new FusionDictionary(new FusionDictionary.PtNodeArray(), header.mDictionaryOptions);
@@ -70,11 +77,11 @@
         final ArrayList<WordProperty> wordProperties = CollectionUtils.newArrayList();
         do {
             final BinaryDictionary.GetNextWordPropertyResult result =
-                    mBinaryDictionary.getNextWordProperty(token);
+                    binaryDictionary.getNextWordProperty(token);
             final WordProperty wordProperty = result.mWordProperty;
             if (wordProperty == null) {
+                binaryDictionary.close();
                 if (deleteDictIfBroken) {
-                    mBinaryDictionary.close();
                     FileUtils.deleteRecursively(mDictDirectory);
                 }
                 return null;
@@ -103,6 +110,7 @@
                 fusionDict.setBigram(word0, bigram.mWord, bigram.mProbabilityInfo);
             }
         }
+        binaryDictionary.close();
         return fusionDict;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
index 6b0bb86..ca8bef3 100644
--- a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
@@ -60,7 +60,7 @@
         return context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
     }
 
-    public static int getCurrentImportantNoticeVersion(final Context context) {
+    private static int getCurrentImportantNoticeVersion(final Context context) {
         return context.getResources().getInteger(R.integer.config_important_notice_version);
     }
 
@@ -68,7 +68,7 @@
         return getImportantNoticePreferences(context).getInt(KEY_IMPORTANT_NOTICE_VERSION, 0);
     }
 
-    private static int getNextImportantNoticeVersion(final Context context) {
+    public static int getNextImportantNoticeVersion(final Context context) {
         return getLastImportantNoticeVersion(context) + 1;
     }
 
@@ -92,23 +92,23 @@
                 .apply();
     }
 
-    // TODO: Make title resource to string array indexed by version.
     public static String getNextImportantNoticeTitle(final Context context) {
-        switch (getNextImportantNoticeVersion(context)) {
-        case VERSION_TO_ENABLE_PERSONALIZED_SUGGESTIONS:
-            return context.getString(R.string.important_notice_title);
-        default:
-            return null;
+        final int nextVersion = getCurrentImportantNoticeVersion(context);
+        final String[] importantNoticeTitleArray = context.getResources().getStringArray(
+                R.array.important_notice_title_array);
+        if (nextVersion > 0 && nextVersion < importantNoticeTitleArray.length) {
+            return importantNoticeTitleArray[nextVersion];
         }
+        return null;
     }
 
-    // TODO: Make content resource to string array indexed by version.
     public static String getNextImportantNoticeContents(final Context context) {
-        switch (getNextImportantNoticeVersion(context)) {
-        case VERSION_TO_ENABLE_PERSONALIZED_SUGGESTIONS:
-            return context.getString(R.string.important_notice_contents);
-        default:
-            return null;
+        final int nextVersion = getNextImportantNoticeVersion(context);
+        final String[] importantNoticeContentsArray = context.getResources().getStringArray(
+                R.array.important_notice_contents_array);
+        if (nextVersion > 0 && nextVersion < importantNoticeContentsArray.length) {
+            return importantNoticeContentsArray[nextVersion];
         }
+        return null;
     }
 }
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 1e00cd8..bb54cbd 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -529,6 +529,14 @@
     return TimeKeeper::peekCurrentTime();
 }
 
+static bool latinime_BinaryDictionary_isCorruptedNative(JNIEnv *env, jclass clazz, jlong dict) {
+    Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
+    if (!dictionary) {
+        return false;
+    }
+    return dictionary->getDictionaryStructurePolicy()->isCorrupted();
+}
+
 static const JNINativeMethod sMethods[] = {
     {
         const_cast<char *>("createEmptyDictFileNative"),
@@ -642,6 +650,11 @@
         const_cast<char *>("getPropertyNative"),
         const_cast<char *>("(JLjava/lang/String;)Ljava/lang/String;"),
         reinterpret_cast<void *>(latinime_BinaryDictionary_getProperty)
+    },
+    {
+        const_cast<char *>("isCorruptedNative"),
+        const_cast<char *>("(J)Z"),
+        reinterpret_cast<void *>(latinime_BinaryDictionary_isCorruptedNative)
     }
 };
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
index 212f2ef..84a6ccf 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
@@ -87,9 +87,24 @@
         int lastCandidatePtNodePos = 0;
         // Let's loop through PtNodes in this PtNode array searching for either the terminal
         // or one of its ascendants.
+        if (pos < 0 || pos >= mDictBufferSize) {
+            AKLOGE("PtNode array position is invalid. pos: %d, dict size: %d",
+                    pos, mDictBufferSize);
+            mIsCorrupted = true;
+            ASSERT(false);
+            *outUnigramProbability = NOT_A_PROBABILITY;
+            return 0;
+        }
         for (int ptNodeCount = PatriciaTrieReadingUtils::getPtNodeArraySizeAndAdvancePosition(
                 mDictRoot, &pos); ptNodeCount > 0; --ptNodeCount) {
             const int startPos = pos;
+            if (pos < 0 || pos >= mDictBufferSize) {
+                AKLOGE("PtNode position is invalid. pos: %d, dict size: %d", pos, mDictBufferSize);
+                mIsCorrupted = true;
+                ASSERT(false);
+                *outUnigramProbability = NOT_A_PROBABILITY;
+                return 0;
+            }
             const PatriciaTrieReadingUtils::NodeFlags flags =
                     PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(mDictRoot, &pos);
             const int character = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
index 1c714e7..ab97513 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -351,6 +351,38 @@
     }
     // TODO: Add some tests for non-BMP characters
 
+    public void testAutoCorrectByUserHistory() {
+        final String WORD_TO_BE_CORRECTED = "qpmx";
+        final String NOT_CORRECTED_RESULT = "qpmx ";
+        final String DESIRED_WORD = "qpmz";
+        final String CORRECTED_RESULT = "qpmz ";
+        final int typeCountNotToAutocorrect = 3;
+        final int typeCountToAutoCorrect = 16;
+        int startIndex = 0;
+        int endIndex = 0;
+
+        for (int i = 0; i < typeCountNotToAutocorrect; i++) {
+            type(DESIRED_WORD);
+            type(Constants.CODE_SPACE);
+        }
+        startIndex = mEditText.getText().length();
+        type(WORD_TO_BE_CORRECTED);
+        type(Constants.CODE_SPACE);
+        endIndex = mEditText.getText().length();
+        assertEquals("not auto-corrected by user history", NOT_CORRECTED_RESULT,
+                mEditText.getText().subSequence(startIndex, endIndex).toString());
+        for (int i = typeCountNotToAutocorrect; i < typeCountToAutoCorrect; i++) {
+            type(DESIRED_WORD);
+            type(Constants.CODE_SPACE);
+        }
+        startIndex = mEditText.getText().length();
+        type(WORD_TO_BE_CORRECTED);
+        type(Constants.CODE_SPACE);
+        endIndex = mEditText.getText().length();
+        assertEquals("auto-corrected by user history",
+                CORRECTED_RESULT, mEditText.getText().subSequence(startIndex, endIndex).toString());
+    }
+
     public void testPredictionsAfterSpace() {
         final String WORD_TO_TYPE = "Barack ";
         type(WORD_TO_TYPE);
diff --git a/tests/src/com/android/inputmethod/latin/settings/SpacingAndPunctuationsTests.java b/tests/src/com/android/inputmethod/latin/settings/SpacingAndPunctuationsTests.java
index 24af094..2cc22fa 100644
--- a/tests/src/com/android/inputmethod/latin/settings/SpacingAndPunctuationsTests.java
+++ b/tests/src/com/android/inputmethod/latin/settings/SpacingAndPunctuationsTests.java
@@ -20,6 +20,8 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.utils.RunInLocale;
 
@@ -32,6 +34,18 @@
     private static final int ARMENIAN_FULL_STOP = '\u0589';
     private static final int ARMENIAN_COMMA = '\u055D';
 
+    private int mScreenMetrics;
+
+    private boolean isPhone() {
+        return mScreenMetrics == Constants.SCREEN_METRICS_SMALL_PHONE
+                || mScreenMetrics == Constants.SCREEN_METRICS_LARGE_PHONE;
+    }
+
+    private boolean isTablet() {
+        return mScreenMetrics == Constants.SCREEN_METRICS_SMALL_TABLET
+                || mScreenMetrics == Constants.SCREEN_METRICS_LARGE_TABLET;
+    }
+
     private SpacingAndPunctuations ENGLISH;
     private SpacingAndPunctuations FRENCH;
     private SpacingAndPunctuations GERMAN;
@@ -56,6 +70,8 @@
     protected void setUp() throws Exception {
         super.setUp();
 
+        mScreenMetrics = mContext.getResources().getInteger(R.integer.config_screen_metrics);
+
         // Language only
         ENGLISH = getSpacingAndPunctuations(Locale.ENGLISH);
         FRENCH = getSpacingAndPunctuations(Locale.FRENCH);
@@ -373,23 +389,39 @@
         assertTrue(SWISS_GERMAN.mUsesGermanRules);
     }
 
-    private static final String[] PUNCTUATION_LABELS_LTR = {
+    // Punctuations for phone.
+    private static final String[] PUNCTUATION_LABELS_PHONE = {
         "!", "?", ",", ":", ";", "\"", "(", ")", "'", "-", "/", "@", "_"
     };
-    private static final String[] PUNCTUATION_WORDS_LTR = PUNCTUATION_LABELS_LTR;
-    private static final String[] PUNCTUATION_WORDS_HEBREW = {
+    private static final String[] PUNCTUATION_WORDS_PHONE_LTR = PUNCTUATION_LABELS_PHONE;
+    private static final String[] PUNCTUATION_WORDS_PHONE_HEBREW = {
         "!", "?", ",", ":", ";", "\"", ")", "(", "'", "-", "/", "@", "_"
     };
     // U+061F: "؟" ARABIC QUESTION MARK
     // U+060C: "،" ARABIC COMMA
     // U+061B: "؛" ARABIC SEMICOLON
-    private static final String[] PUNCTUATION_LABELS_ARABIC_PERSIAN = {
+    private static final String[] PUNCTUATION_LABELS_PHONE_ARABIC_PERSIAN = {
         "!", "\u061F", "\u060C", ":", "\u061B", "\"", "(", ")", "'", "-", "/", "@", "_"
     };
-    private static final String[] PUNCTUATION_WORDS_ARABIC_PERSIAN = {
+    private static final String[] PUNCTUATION_WORDS_PHONE_ARABIC_PERSIAN = {
         "!", "\u061F", "\u060C", ":", "\u061B", "\"", ")", "(", "'", "-", "/", "@", "_"
     };
 
+    // Punctuations for tablet.
+    private static final String[] PUNCTUATION_LABELS_TABLET = {
+        ":", ";", "\"", "(", ")", "'", "-", "/", "@", "_"
+    };
+    private static final String[] PUNCTUATION_WORDS_TABLET_LTR = PUNCTUATION_LABELS_TABLET;
+    private static final String[] PUNCTUATION_WORDS_TABLET_HEBREW = {
+        ":", ";", "\"", ")", "(", "'", "-", "/", "@", "_"
+    };
+    private static final String[] PUNCTUATION_LABELS_TABLET_ARABIC_PERSIAN = {
+        "!", "\u061F", ":", "\u061B", "\"", "'", "(", ")",  "-", "/", "@", "_"
+    };
+    private static final String[] PUNCTUATION_WORDS_TABLET_ARABIC_PERSIAN = {
+        "!", "\u061F", ":", "\u061B", "\"", "'", ")", "(",  "-", "/", "@", "_"
+    };
+
     private static void testingStandardPunctuationSuggestions(final SpacingAndPunctuations sp,
             final String[] punctuationLabels, final String[] punctuationWords) {
         final SuggestedWords suggestedWords = sp.mSuggestPuncList;
@@ -407,19 +439,39 @@
         }
     }
 
-    // TODO: Add tests for tablet as well
-    public void testPunctuationSuggestions() {
+    public void testPhonePunctuationSuggestions() {
+        if (!isPhone()) {
+            return;
+        }
         testingStandardPunctuationSuggestions(ENGLISH,
-                PUNCTUATION_LABELS_LTR, PUNCTUATION_WORDS_LTR);
+                PUNCTUATION_LABELS_PHONE, PUNCTUATION_WORDS_PHONE_LTR);
         testingStandardPunctuationSuggestions(FRENCH,
-                PUNCTUATION_LABELS_LTR, PUNCTUATION_WORDS_LTR);
+                PUNCTUATION_LABELS_PHONE, PUNCTUATION_WORDS_PHONE_LTR);
         testingStandardPunctuationSuggestions(GERMAN,
-                PUNCTUATION_LABELS_LTR, PUNCTUATION_WORDS_LTR);
+                PUNCTUATION_LABELS_PHONE, PUNCTUATION_WORDS_PHONE_LTR);
         testingStandardPunctuationSuggestions(ARABIC,
-                PUNCTUATION_LABELS_ARABIC_PERSIAN, PUNCTUATION_WORDS_ARABIC_PERSIAN);
+                PUNCTUATION_LABELS_PHONE_ARABIC_PERSIAN, PUNCTUATION_WORDS_PHONE_ARABIC_PERSIAN);
         testingStandardPunctuationSuggestions(PERSIAN,
-                PUNCTUATION_LABELS_ARABIC_PERSIAN, PUNCTUATION_WORDS_ARABIC_PERSIAN);
+                PUNCTUATION_LABELS_PHONE_ARABIC_PERSIAN, PUNCTUATION_WORDS_PHONE_ARABIC_PERSIAN);
         testingStandardPunctuationSuggestions(HEBREW,
-                PUNCTUATION_LABELS_LTR, PUNCTUATION_WORDS_HEBREW);
+                PUNCTUATION_LABELS_PHONE, PUNCTUATION_WORDS_PHONE_HEBREW);
+    }
+
+    public void testTabletPunctuationSuggestions() {
+        if (!isTablet()) {
+            return;
+        }
+        testingStandardPunctuationSuggestions(ENGLISH,
+                PUNCTUATION_LABELS_TABLET, PUNCTUATION_WORDS_TABLET_LTR);
+        testingStandardPunctuationSuggestions(FRENCH,
+                PUNCTUATION_LABELS_TABLET, PUNCTUATION_WORDS_TABLET_LTR);
+        testingStandardPunctuationSuggestions(GERMAN,
+                PUNCTUATION_LABELS_TABLET, PUNCTUATION_WORDS_TABLET_LTR);
+        testingStandardPunctuationSuggestions(ARABIC,
+                PUNCTUATION_LABELS_TABLET_ARABIC_PERSIAN, PUNCTUATION_WORDS_TABLET_ARABIC_PERSIAN);
+        testingStandardPunctuationSuggestions(PERSIAN,
+                PUNCTUATION_LABELS_TABLET_ARABIC_PERSIAN, PUNCTUATION_WORDS_TABLET_ARABIC_PERSIAN);
+        testingStandardPunctuationSuggestions(HEBREW,
+                PUNCTUATION_LABELS_TABLET, PUNCTUATION_WORDS_TABLET_HEBREW);
     }
 }
diff --git a/tools/make-keyboard-text/res/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.tmpl b/tools/make-keyboard-text/res/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.tmpl
similarity index 97%
rename from tools/make-keyboard-text/res/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.tmpl
rename to tools/make-keyboard-text/res/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.tmpl
index 7ec42ce..b25bfb2 100644
--- a/tools/make-keyboard-text/res/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.tmpl
+++ b/tools/make-keyboard-text/res/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.tmpl
@@ -24,7 +24,8 @@
  * !!!!! DO NOT EDIT THIS FILE !!!!!
  *
  * This file is generated by tools/make-keyboard-text. The base template file is
- *   tools/make-keyboard-text/res/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.tmpl
+ *   tools/make-keyboard-text/res/src/com/android/inputmethod/keyboard/internal/
+ *   KeyboardTextsTable.tmpl
  *
  * This file must be updated when any text resources in keyboard layout files have been changed.
  * These text resources are referred as "!text/<resource_name>" in keyboard XML definitions,
@@ -34,7 +35,7 @@
  * To update this file, please run the following commands.
  *   $ cd $ANDROID_BUILD_TOP
  *   $ mmm packages/inputmethods/LatinIME/tools/make-keyboard-text
- *   $ make-keyboard-text -java packages/inputmethods/LatinIME/java/src
+ *   $ make-keyboard-text -java packages/inputmethods/LatinIME/java
  *
  * The updated source file will be generated to the following path (this file).
  *   packages/inputmethods/LatinIME/java/src/com/android/inputmethod/keyboard/internal/
diff --git a/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/MoreKeysResources.java b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/MoreKeysResources.java
index e9d6c86..9bb2b38 100644
--- a/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/MoreKeysResources.java
+++ b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/MoreKeysResources.java
@@ -29,7 +29,6 @@
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.jar.JarFile;
-import java.util.regex.Pattern;
 
 public class MoreKeysResources {
     private static final String TEXT_RESOURCE_NAME = "donottranslate-more-keys.xml";