Merge "Import translations. DO NOT MERGE"
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 27ff6cd..1bc8254 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -77,27 +77,6 @@
         mNativeProximityInfo = createNativeProximityInfo();
     }
 
-    // TODO: Remove this public constructor when the native part of the ProximityInfo becomes
-    // immutable.
-    // This public constructor aims only for test purpose.
-    public ProximityInfo(ProximityInfo o) {
-        mLocaleStr = o.mLocaleStr;
-        mGridWidth = o.mGridWidth;
-        mGridHeight = o.mGridHeight;
-        mGridSize = o.mGridSize;
-        mCellWidth = o.mCellWidth;
-        mCellHeight = o.mCellHeight;
-        mKeyboardMinWidth = o.mKeyboardMinWidth;
-        mKeyboardHeight = o.mKeyboardHeight;
-        mKeyHeight = o.mKeyHeight;
-        mMostCommonKeyWidth = o.mMostCommonKeyWidth;
-        mKeys = o.mKeys;
-        mTouchPositionCorrection = o.mTouchPositionCorrection;
-        mGridNeighbors = new Key[mGridSize][];
-        computeNearestNeighbors();
-        mNativeProximityInfo = createNativeProximityInfo();
-    }
-
     public static ProximityInfo createDummyProximityInfo() {
         return new ProximityInfo("", 1, 1, 1, 1, 1, 1, EMPTY_KEY_ARRAY, null);
     }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 94e0ac8..5236591 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -179,7 +179,6 @@
 
     public static class UIHandler extends StaticInnerHandlerWrapper<LatinIME> {
         private static final int MSG_UPDATE_SHIFT_STATE = 1;
-        private static final int MSG_SPACE_TYPED = 4;
         private static final int MSG_SET_BIGRAM_PREDICTIONS = 5;
         private static final int MSG_PENDING_IMS_CALLBACK = 6;
         private static final int MSG_UPDATE_SUGGESTIONS = 7;
@@ -187,6 +186,7 @@
         private int mDelayUpdateSuggestions;
         private int mDelayUpdateShiftState;
         private long mDoubleSpacesTurnIntoPeriodTimeout;
+        private long mDoubleSpaceTimerStart;
 
         public UIHandler(LatinIME outerInstance) {
             super(outerInstance);
@@ -251,16 +251,16 @@
         }
 
         public void startDoubleSpacesTimer() {
-            removeMessages(MSG_SPACE_TYPED);
-            sendMessageDelayed(obtainMessage(MSG_SPACE_TYPED), mDoubleSpacesTurnIntoPeriodTimeout);
+            mDoubleSpaceTimerStart = SystemClock.uptimeMillis();
         }
 
         public void cancelDoubleSpacesTimer() {
-            removeMessages(MSG_SPACE_TYPED);
+            mDoubleSpaceTimerStart = 0;
         }
 
         public boolean isAcceptingDoubleSpaces() {
-            return hasMessages(MSG_SPACE_TYPED);
+            return SystemClock.uptimeMillis() - mDoubleSpaceTimerStart
+                    < mDoubleSpacesTurnIntoPeriodTimeout;
         }
 
         // Working variables for the following methods.
@@ -391,6 +391,8 @@
 
         Utils.GCUtils.getInstance().reset();
         boolean tryGC = true;
+        // Shouldn't this be removed? I think that from Honeycomb on, the GC is now actually working
+        // as expected and this code is useless.
         for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
             try {
                 initSuggest();
@@ -449,7 +451,7 @@
             oldContactsDictionary = null;
         }
         mSuggest = new Suggest(this, subtypeLocale);
-        if (mCurrentSettings.mAutoCorrectEnabled) {
+        if (mCurrentSettings.isCorrectionOn()) {
             mSuggest.setAutoCorrectionThreshold(mCurrentSettings.mAutoCorrectionThreshold);
         }
 
@@ -680,7 +682,7 @@
 
         loadSettings();
 
-        if (mSuggest != null && mCurrentSettings.mAutoCorrectEnabled) {
+        if (mSuggest != null && mCurrentSettings.isCorrectionOn()) {
             mSuggest.setAutoCorrectionThreshold(mCurrentSettings.mAutoCorrectionThreshold);
         }
 
@@ -762,6 +764,7 @@
                     composingSpanEnd, mExpectingUpdateSelection,
                     expectingUpdateSelectionFromLogger, mConnection);
             if (expectingUpdateSelectionFromLogger) {
+                // TODO: Investigate. Quitting now sounds wrong - we won't do the resetting work
                 return;
             }
         }
@@ -1072,12 +1075,12 @@
 
     private boolean maybeDoubleSpace() {
         if (mCurrentSettings.mCorrectionMode == Suggest.CORRECTION_NONE) return false;
+        if (!mHandler.isAcceptingDoubleSpaces()) return false;
         final CharSequence lastThree = mConnection.getTextBeforeCursor(3, 0);
         if (lastThree != null && lastThree.length() == 3
                 && canBeFollowedByPeriod(lastThree.charAt(0))
                 && lastThree.charAt(1) == Keyboard.CODE_SPACE
-                && lastThree.charAt(2) == Keyboard.CODE_SPACE
-                && mHandler.isAcceptingDoubleSpaces()) {
+                && lastThree.charAt(2) == Keyboard.CODE_SPACE) {
             mHandler.cancelDoubleSpacesTimer();
             mConnection.deleteSurroundingText(2, 0);
             mConnection.commitText(". ", 1);
@@ -1553,8 +1556,7 @@
             // not to auto correct, but accept the typed word. For instance,
             // in Italian dov' should not be expanded to dove' because the elision
             // requires the last vowel to be removed.
-            final boolean shouldAutoCorrect = mCurrentSettings.mAutoCorrectEnabled
-                    && !mInputAttributes.mInputTypeNoAutoCorrect;
+            final boolean shouldAutoCorrect = mCurrentSettings.isCorrectionOn();
             if (shouldAutoCorrect && primaryCode != Keyboard.CODE_SINGLE_QUOTE) {
                 commitCurrentAutoCorrection(primaryCode);
                 didAutoCorrect = true;
@@ -1627,7 +1629,7 @@
     public boolean isSuggestionsRequested() {
         // TODO: move this method to mSettingsValues
         return mInputAttributes.mIsSettingsSuggestionStripOn
-                && (mCurrentSettings.mCorrectionMode > 0 || isShowingSuggestionsStrip());
+                && (mCurrentSettings.isCorrectionOn() || isShowingSuggestionsStrip());
     }
 
     public boolean isShowingPunctuationList() {
@@ -1914,14 +1916,11 @@
             mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
                     this, chosenWord, suggestedWords, mIsMainDictionaryAvailable),
                     1);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_commitText(chosenWord);
-            }
         } else {
             mConnection.commitText(chosenWord, 1);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_commitText(chosenWord);
-            }
+        }
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.latinIME_commitText(chosenWord);
         }
         // Add the word to the user history dictionary
         final CharSequence prevWord = addToUserHistoryDictionary(chosenWord);
@@ -2231,7 +2230,7 @@
         p.println("  mIsSuggestionsRequested=" + mInputAttributes.mIsSettingsSuggestionStripOn);
         p.println("  mCorrectionMode=" + mCurrentSettings.mCorrectionMode);
         p.println("  isComposingWord=" + mWordComposer.isComposingWord());
-        p.println("  mAutoCorrectEnabled=" + mCurrentSettings.mAutoCorrectEnabled);
+        p.println("  isCorrectionOn=" + mCurrentSettings.isCorrectionOn());
         p.println("  mSoundOn=" + mCurrentSettings.mSoundOn);
         p.println("  mVibrateOn=" + mCurrentSettings.mVibrateOn);
         p.println("  mKeyPreviewPopupOn=" + mCurrentSettings.mKeyPreviewPopupOn);
diff --git a/java/src/com/android/inputmethod/latin/ResearchLogger.java b/java/src/com/android/inputmethod/latin/ResearchLogger.java
index f27d070..46efa78 100644
--- a/java/src/com/android/inputmethod/latin/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/latin/ResearchLogger.java
@@ -40,6 +40,7 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.latin.RichInputConnection.Range;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
 
@@ -614,11 +615,17 @@
             final int composingSpanEnd, final boolean expectingUpdateSelection,
             final boolean expectingUpdateSelectionFromLogger,
             final RichInputConnection connection) {
+        String word = "";
+        if (connection != null) {
+            Range range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
+            if (range != null) {
+                word = range.mWord;
+            }
+        }
         final Object[] values = {
             lastSelectionStart, lastSelectionEnd, oldSelStart, oldSelEnd, newSelStart,
             newSelEnd, composingSpanStart, composingSpanEnd, expectingUpdateSelection,
-            expectingUpdateSelectionFromLogger,
-            connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1).mWord
+            expectingUpdateSelectionFromLogger, word
         };
         getInstance().writeEvent(EVENTKEYS_LATINIME_ONUPDATESELECTION, values);
     }
@@ -637,9 +644,9 @@
         "LatinIMEPickApplicationSpecifiedCompletion", "index", "text", "x", "y"
     };
     public static void latinIME_pickApplicationSpecifiedCompletion(final int index,
-            final CharSequence text, int x, int y) {
+            final CharSequence cs, int x, int y) {
         final Object[] values = {
-            index, text.toString(), x, y
+            index, cs, x, y
         };
         getInstance().writeEvent(EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION, values);
     }
@@ -650,7 +657,7 @@
     public static void latinIME_pickSuggestionManually(final String replacedWord,
             final int index, CharSequence suggestion, int x, int y) {
         final Object[] values = {
-            replacedWord, index, suggestion.toString(), x, y
+            replacedWord, index, suggestion, x, y
         };
         getInstance().writeEvent(EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY, values);
     }
@@ -661,7 +668,7 @@
     public static void latinIME_punctuationSuggestion(final int index,
             final CharSequence suggestion, int x, int y) {
         final Object[] values = {
-            index, suggestion.toString(), x, y
+            index, suggestion, x, y
         };
         getInstance().writeEvent(EVENTKEYS_LATINIME_PUNCTUATIONSUGGESTION, values);
     }
@@ -773,8 +780,8 @@
         if (key != null) {
             CharSequence outputText = key.mOutputText;
             final Object[] values = {
-                Keyboard.printableCode(code), outputText == null ? "" : outputText.toString(),
-                x, y, ignoreModifierKey, altersCode, key.isEnabled()
+                Keyboard.printableCode(code), outputText, x, y, ignoreModifierKey, altersCode,
+                key.isEnabled()
             };
             getInstance().writeEvent(EVENTKEYS_POINTERTRACKER_CALLLISTENERONCODEINPUT, values);
         }
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 10094b5..6a79aa6 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -91,7 +91,7 @@
     public final int mKeypressVibrationDuration;
     public final float mFxVolume;
     public final int mKeyPreviewPopupDismissDelay;
-    public final boolean mAutoCorrectEnabled;
+    private final boolean mAutoCorrectEnabled;
     public final float mAutoCorrectionThreshold;
     public final int mCorrectionMode;
     public final int mSuggestionVisibility;
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
index 10f92d2..5095f65 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
@@ -32,6 +32,7 @@
 import java.lang.ref.SoftReference;
 import java.util.HashMap;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * Locally gathers stats about the words user types and various other signals like auto-correction
@@ -40,6 +41,8 @@
 public class UserHistoryDictionary extends ExpandableDictionary {
     private static final String TAG = "UserHistoryDictionary";
     public static final boolean DBG_SAVE_RESTORE = false;
+    public static final boolean DBG_STRESS_TEST = false;
+    public static final boolean DBG_ALWAYS_WRITE = false;
     public static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
 
     /** Any pair being typed or picked */
@@ -82,7 +85,7 @@
 
     private final UserHistoryDictionaryBigramList mBigramList =
             new UserHistoryDictionaryBigramList();
-    private static volatile boolean sUpdatingDB = false;
+    private final ReentrantLock mBigramListLock = new ReentrantLock();
     private final SharedPreferences mPrefs;
 
     private final static HashMap<String, String> sDictProjectionMap;
@@ -173,28 +176,38 @@
      * The second word may not be null (a NullPointerException would be thrown).
      */
     public int addToUserHistory(final String word1, String word2, boolean isValid) {
-        super.addWord(word2, null /* the "shortcut" parameter is null */, FREQUENCY_FOR_TYPED);
-        // Do not insert a word as a bigram of itself
-        if (word2.equals(word1)) {
-            return 0;
+        if (mBigramListLock.tryLock()) {
+            try {
+                super.addWord(
+                        word2, null /* the "shortcut" parameter is null */, FREQUENCY_FOR_TYPED);
+                // Do not insert a word as a bigram of itself
+                if (word2.equals(word1)) {
+                    return 0;
+                }
+                final int freq;
+                if (null == word1) {
+                    freq = FREQUENCY_FOR_TYPED;
+                } else {
+                    freq = super.setBigramAndGetFrequency(
+                            word1, word2, new ForgettingCurveParams(isValid));
+                }
+                mBigramList.addBigram(word1, word2);
+                return freq;
+            } finally {
+                mBigramListLock.unlock();
+            }
         }
-        final int freq;
-        if (null == word1) {
-            freq = FREQUENCY_FOR_TYPED;
-        } else {
-            freq = super.setBigramAndGetFrequency(word1, word2, new ForgettingCurveParams(isValid));
-        }
-        synchronized (mBigramList) {
-            mBigramList.addBigram(word1, word2);
-        }
-
-        return freq;
+        return -1;
     }
 
     public boolean cancelAddingUserHistory(String word1, String word2) {
-        synchronized (mBigramList) {
-            if (mBigramList.removeBigram(word1, word2)) {
-                return super.removeBigram(word1, word2);
+        if (mBigramListLock.tryLock()) {
+            try {
+                if (mBigramList.removeBigram(word1, word2)) {
+                    return super.removeBigram(word1, word2);
+                }
+            } finally {
+                mBigramListLock.unlock();
             }
         }
         return false;
@@ -204,70 +217,73 @@
      * Schedules a background thread to write any pending words to the database.
      */
     private void flushPendingWrites() {
-        synchronized (mBigramList) {
-            // Nothing pending? Return
-            if (mBigramList.isEmpty()) return;
-            // Create a background thread to write the pending entries
-            new UpdateDbTask(sOpenHelper, mBigramList, mLocale, this, mPrefs).execute();
-        }
-    }
-
-    /** Used for testing purpose **/
-    void waitUntilUpdateDBDone() {
-        synchronized (mBigramList) {
-            while (sUpdatingDB) {
-                try {
-                    Thread.sleep(100);
-                } catch (InterruptedException e) {
-                }
-            }
+        if (mBigramListLock.isLocked()) {
             return;
         }
+        // Create a background thread to write the pending entries
+        new UpdateDbTask(sOpenHelper, mBigramList, mLocale, this, mPrefs).execute();
     }
 
     @Override
     public void loadDictionaryAsync() {
-        synchronized(mBigramList) {
-            final long last = SettingsValues.getLastUserHistoryWriteTime(mPrefs, mLocale);
-            final boolean initializing = last == 0;
-            final long now = System.currentTimeMillis();
-            // Load the words that correspond to the current input locale
-            final Cursor cursor = query(MAIN_COLUMN_LOCALE + "=?", new String[] { mLocale });
-            if (null == cursor) return;
+        // This must be run on non-main thread
+        mBigramListLock.lock();
+        try {
+            loadDictionaryAsyncLocked();
+        } finally {
+            mBigramListLock.unlock();
+        }
+    }
+
+    private void loadDictionaryAsyncLocked() {
+        if (DBG_STRESS_TEST) {
             try {
-                if (cursor.moveToFirst()) {
-                    final int word1Index = cursor.getColumnIndex(MAIN_COLUMN_WORD1);
-                    final int word2Index = cursor.getColumnIndex(MAIN_COLUMN_WORD2);
-                    final int fcIndex = cursor.getColumnIndex(COLUMN_FORGETTING_CURVE_VALUE);
-                    while (!cursor.isAfterLast()) {
-                        final String word1 = cursor.getString(word1Index);
-                        final String word2 = cursor.getString(word2Index);
-                        final int fc = cursor.getInt(fcIndex);
-                        if (DBG_SAVE_RESTORE) {
-                            Log.d(TAG, "--- Load user history: " + word1 + ", " + word2 + ","
-                                    + mLocale + "," + this);
-                        }
-                        // Safeguard against adding really long words. Stack may overflow due
-                        // to recursive lookup
-                        if (null == word1) {
-                            super.addWord(word2, null /* shortcut */, fc);
-                        } else if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH
-                                && word2.length() < BinaryDictionary.MAX_WORD_LENGTH) {
-                            super.setBigramAndGetFrequency(
-                                    word1, word2, initializing ? new ForgettingCurveParams(true)
-                                            : new ForgettingCurveParams(fc, now, last));
-                        }
-                        mBigramList.addBigram(word1, word2, (byte)fc);
-                        cursor.moveToNext();
+                Log.w(TAG, "Start stress in loading: " + mLocale);
+                Thread.sleep(15000);
+                Log.w(TAG, "End stress in loading");
+            } catch (InterruptedException e) {
+            }
+        }
+        final long last = SettingsValues.getLastUserHistoryWriteTime(mPrefs, mLocale);
+        final boolean initializing = last == 0;
+        final long now = System.currentTimeMillis();
+        // Load the words that correspond to the current input locale
+        final Cursor cursor = query(MAIN_COLUMN_LOCALE + "=?", new String[] { mLocale });
+        if (null == cursor) return;
+        try {
+            // TODO: Call SQLiteDataBase.beginTransaction / SQLiteDataBase.endTransaction
+            if (cursor.moveToFirst()) {
+                final int word1Index = cursor.getColumnIndex(MAIN_COLUMN_WORD1);
+                final int word2Index = cursor.getColumnIndex(MAIN_COLUMN_WORD2);
+                final int fcIndex = cursor.getColumnIndex(COLUMN_FORGETTING_CURVE_VALUE);
+                while (!cursor.isAfterLast()) {
+                    final String word1 = cursor.getString(word1Index);
+                    final String word2 = cursor.getString(word2Index);
+                    final int fc = cursor.getInt(fcIndex);
+                    if (DBG_SAVE_RESTORE) {
+                        Log.d(TAG, "--- Load user history: " + word1 + ", " + word2 + ","
+                                + mLocale + "," + this);
                     }
+                    // Safeguard against adding really long words. Stack may overflow due
+                    // to recursive lookup
+                    if (null == word1) {
+                        super.addWord(word2, null /* shortcut */, fc);
+                    } else if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH
+                            && word2.length() < BinaryDictionary.MAX_WORD_LENGTH) {
+                        super.setBigramAndGetFrequency(
+                                word1, word2, initializing ? new ForgettingCurveParams(true)
+                                : new ForgettingCurveParams(fc, now, last));
+                    }
+                    mBigramList.addBigram(word1, word2, (byte)fc);
+                    cursor.moveToNext();
                 }
-            } finally {
-                cursor.close();
-                if (PROFILE_SAVE_RESTORE) {
-                    final long diff = System.currentTimeMillis() - now;
-                    Log.w(TAG, "PROF: Load User HistoryDictionary: "
-                            + mLocale + ", " + diff + "ms.");
-                }
+            }
+        } finally {
+            cursor.close();
+            if (PROFILE_SAVE_RESTORE) {
+                final long diff = System.currentTimeMillis() - now;
+                Log.w(TAG, "PROF: Load User HistoryDictionary: "
+                        + mLocale + ", " + diff + "ms.");
             }
         }
     }
@@ -388,146 +404,167 @@
         }
 
         @Override
-        protected void onPreExecute() {
-            sUpdatingDB = true;
+        protected Void doInBackground(Void... v) {
+            SQLiteDatabase db = null;
+            if (mUserHistoryDictionary.mBigramListLock.tryLock()) {
+                try {
+                    try {
+                        db = mDbHelper.getWritableDatabase();
+                    } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) {
+                        // If we can't open the db, don't do anything. Exit through the next test
+                        // for non-nullity of the db variable.
+                    }
+                    if (null == db) {
+                        // Not much we can do. Just exit.
+                        return null;
+                    }
+                    db.beginTransaction();
+                    return doLoadTaskLocked(db);
+                } finally {
+                    if (db != null) {
+                        db.endTransaction();
+                    }
+                    mUserHistoryDictionary.mBigramListLock.unlock();
+                }
+            }
+            return null;
         }
 
-        @Override
-        protected Void doInBackground(Void... v) {
-            synchronized(mBigramList) {
-                final long now = PROFILE_SAVE_RESTORE ? System.currentTimeMillis() : 0;
-                int profTotal = 0;
-                int profInsert = 0;
-                int profDelete = 0;
-                SQLiteDatabase db = null;
+        private Void doLoadTaskLocked(SQLiteDatabase db) {
+            if (DBG_STRESS_TEST) {
                 try {
-                    db = mDbHelper.getWritableDatabase();
-                } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) {
-                    // If we can't open the db, don't do anything. Exit through the next test
-                    // for non-nullity of the db variable.
+                    Log.w(TAG, "Start stress in closing: " + mLocale);
+                    Thread.sleep(15000);
+                    Log.w(TAG, "End stress in closing");
+                } catch (InterruptedException e) {
                 }
-                if (null == db) {
-                    // Not much we can do. Just exit.
-                    sUpdatingDB = false;
-                    return null;
-                }
-                db.execSQL("PRAGMA foreign_keys = ON;");
-                final boolean addLevel0Bigram = mBigramList.size() <= sMaxHistoryBigrams;
+            }
+            final long now = PROFILE_SAVE_RESTORE ? System.currentTimeMillis() : 0;
+            int profTotal = 0;
+            int profInsert = 0;
+            int profDelete = 0;
+            db.execSQL("PRAGMA foreign_keys = ON;");
+            final boolean addLevel0Bigram = mBigramList.size() <= sMaxHistoryBigrams;
 
-                // Write all the entries to the db
-                for (String word1 : mBigramList.keySet()) {
-                    final HashMap<String, Byte> word1Bigrams = mBigramList.getBigrams(word1);
-                    for (String word2 : word1Bigrams.keySet()) {
-                        if (PROFILE_SAVE_RESTORE) {
-                            ++profTotal;
+            // Write all the entries to the db
+            for (String word1 : mBigramList.keySet()) {
+                final HashMap<String, Byte> word1Bigrams = mBigramList.getBigrams(word1);
+                for (String word2 : word1Bigrams.keySet()) {
+                    if (PROFILE_SAVE_RESTORE) {
+                        ++profTotal;
+                    }
+                    // Get new frequency. Do not insert unigrams/bigrams which freq is "-1".
+                    final int freq; // -1, or 0~255
+                    if (word1 == null) { // unigram
+                        freq = FREQUENCY_FOR_TYPED;
+                        final byte prevFc = word1Bigrams.get(word2);
+                        if (prevFc == FREQUENCY_FOR_TYPED) {
+                            // No need to update since we found no changes for this entry.
+                            // Just skip to the next entry.
+                            if (DBG_SAVE_RESTORE) {
+                                Log.d(TAG, "Skip update user history: " + word1 + "," + word2
+                                        + "," + prevFc);
+                            }
+                            if (!DBG_ALWAYS_WRITE) {
+                                continue;
+                            }
                         }
-                        // Get new frequency. Do not insert unigrams/bigrams which freq is "-1".
-                        final int freq; // -1, or 0~255
-                        if (word1 == null) { // unigram
-                            freq = FREQUENCY_FOR_TYPED;
+                    } else { // bigram
+                        final NextWord nw = mUserHistoryDictionary.getBigramWord(word1, word2);
+                        if (nw != null) {
+                            final ForgettingCurveParams fcp = nw.getFcParams();
                             final byte prevFc = word1Bigrams.get(word2);
-                            if (prevFc == FREQUENCY_FOR_TYPED) {
+                            final byte fc = (byte)fcp.getFc();
+                            final boolean isValid = fcp.isValid();
+                            if (prevFc > 0 && prevFc == fc) {
                                 // No need to update since we found no changes for this entry.
                                 // Just skip to the next entry.
                                 if (DBG_SAVE_RESTORE) {
-                                    Log.d(TAG, "Skip update user history: " + word1 + "," + word2
-                                            + "," + prevFc);
+                                    Log.d(TAG, "Skip update user history: " + word1 + ","
+                                            + word2 + "," + prevFc);
                                 }
-                                continue;
-                            }
-                        } else { // bigram
-                            final NextWord nw = mUserHistoryDictionary.getBigramWord(word1, word2);
-                            if (nw != null) {
-                                final ForgettingCurveParams fcp = nw.getFcParams();
-                                final byte prevFc = word1Bigrams.get(word2);
-                                final byte fc = fcp.getFc();
-                                final boolean isValid = fcp.isValid();
-                                if (prevFc > 0 && prevFc == fc) {
-                                    // No need to update since we found no changes for this entry.
-                                    // Just skip to the next entry.
-                                    if (DBG_SAVE_RESTORE) {
-                                        Log.d(TAG, "Skip update user history: " + word1 + ","
-                                                + word2 + "," + prevFc);
-                                    }
+                                if (!DBG_ALWAYS_WRITE) {
                                     continue;
-                                } else if (UserHistoryForgettingCurveUtils.
-                                        needsToSave(fc, isValid, addLevel0Bigram)) {
-                                    freq = fc;
                                 } else {
-                                    freq = -1;
+                                    freq = fc;
                                 }
+                            } else if (UserHistoryForgettingCurveUtils.
+                                    needsToSave(fc, isValid, addLevel0Bigram)) {
+                                freq = fc;
                             } else {
                                 freq = -1;
                             }
+                        } else {
+                            freq = -1;
                         }
-                        // TODO: this process of making a text search for each pair each time
-                        // is terribly inefficient. Optimize this.
-                        // Find pair id
-                        Cursor c = null;
-                        try {
-                            if (null != word1) {
-                                c = db.query(MAIN_TABLE_NAME, new String[] { MAIN_COLUMN_ID },
-                                        MAIN_COLUMN_WORD1 + "=? AND " + MAIN_COLUMN_WORD2 + "=? AND "
-                                                + MAIN_COLUMN_LOCALE + "=?",
-                                                new String[] { word1, word2, mLocale }, null, null,
-                                                null);
-                            } else {
-                                c = db.query(MAIN_TABLE_NAME, new String[] { MAIN_COLUMN_ID },
-                                        MAIN_COLUMN_WORD1 + " IS NULL AND " + MAIN_COLUMN_WORD2
-                                                + "=? AND " + MAIN_COLUMN_LOCALE + "=?",
-                                                new String[] { word2, mLocale }, null, null, null);
-                            }
+                    }
+                    // TODO: this process of making a text search for each pair each time
+                    // is terribly inefficient. Optimize this.
+                    // Find pair id
+                    Cursor c = null;
+                    try {
+                        if (null != word1) {
+                            c = db.query(MAIN_TABLE_NAME, new String[] { MAIN_COLUMN_ID },
+                                    MAIN_COLUMN_WORD1 + "=? AND " + MAIN_COLUMN_WORD2 + "=? AND "
+                                            + MAIN_COLUMN_LOCALE + "=?",
+                                            new String[] { word1, word2, mLocale }, null, null,
+                                            null);
+                        } else {
+                            c = db.query(MAIN_TABLE_NAME, new String[] { MAIN_COLUMN_ID },
+                                    MAIN_COLUMN_WORD1 + " IS NULL AND " + MAIN_COLUMN_WORD2
+                                    + "=? AND " + MAIN_COLUMN_LOCALE + "=?",
+                                    new String[] { word2, mLocale }, null, null, null);
+                        }
 
-                            final int pairId;
-                            if (c.moveToFirst()) {
-                                if (PROFILE_SAVE_RESTORE) {
-                                    ++profDelete;
-                                }
-                                // Delete existing pair
-                                pairId = c.getInt(c.getColumnIndex(MAIN_COLUMN_ID));
-                                db.delete(FREQ_TABLE_NAME, FREQ_COLUMN_PAIR_ID + "=?",
-                                        new String[] { Integer.toString(pairId) });
-                            } else {
-                                // Create new pair
-                                Long pairIdLong = db.insert(MAIN_TABLE_NAME, null,
-                                        getContentValues(word1, word2, mLocale));
-                                pairId = pairIdLong.intValue();
+                        final int pairId;
+                        if (c.moveToFirst()) {
+                            if (PROFILE_SAVE_RESTORE) {
+                                ++profDelete;
                             }
-                            if (freq > 0) {
-                                if (PROFILE_SAVE_RESTORE) {
-                                    ++profInsert;
-                                }
-                                if (DBG_SAVE_RESTORE) {
-                                    Log.d(TAG, "--- Save user history: " + word1 + ", " + word2
-                                            + mLocale + "," + this);
-                                }
-                                // Insert new frequency
-                                db.insert(FREQ_TABLE_NAME, null,
-                                        getFrequencyContentValues(pairId, freq));
-                                // Update an existing bigram entry in mBigramList too in order to
-                                // synchronize the SQL DB and mBigramList.
-                                mBigramList.updateBigram(word1, word2, (byte)freq);
+                            // Delete existing pair
+                            pairId = c.getInt(c.getColumnIndex(MAIN_COLUMN_ID));
+                            db.delete(FREQ_TABLE_NAME, FREQ_COLUMN_PAIR_ID + "=?",
+                                    new String[] { Integer.toString(pairId) });
+                        } else {
+                            // Create new pair
+                            Long pairIdLong = db.insert(MAIN_TABLE_NAME, null,
+                                    getContentValues(word1, word2, mLocale));
+                            pairId = pairIdLong.intValue();
+                        }
+                        if (freq > 0) {
+                            if (PROFILE_SAVE_RESTORE) {
+                                ++profInsert;
                             }
-                        } finally {
-                            if (c != null) {
-                                c.close();
+                            if (DBG_SAVE_RESTORE) {
+                                Log.d(TAG, "--- Save user history: " + word1 + ", " + word2
+                                        + mLocale + "," + this);
                             }
+                            // Insert new frequency
+                            db.insert(FREQ_TABLE_NAME, null,
+                                    getFrequencyContentValues(pairId, freq));
+                            // Update an existing bigram entry in mBigramList too in order to
+                            // synchronize the SQL DB and mBigramList.
+                            mBigramList.updateBigram(word1, word2, (byte)freq);
+                        }
+                    } finally {
+                        if (c != null) {
+                            c.close();
                         }
                     }
                 }
+            }
 
-                checkPruneData(db);
-                // Save the timestamp after we finish writing the SQL DB.
-                SettingsValues.setLastUserHistoryWriteTime(mPrefs, mLocale);
-                sUpdatingDB = false;
-                if (PROFILE_SAVE_RESTORE) {
-                    final long diff = System.currentTimeMillis() - now;
-                    Log.w(TAG, "PROF: Write User HistoryDictionary: " + mLocale + ", "+ diff
-                            + "ms. Total: " + profTotal + ". Insert: " + profInsert + ". Delete: "
-                            + profDelete);
-                }
-                return null;
-            } // synchronized
+            checkPruneData(db);
+            // Save the timestamp after we finish writing the SQL DB.
+            SettingsValues.setLastUserHistoryWriteTime(mPrefs, mLocale);
+            if (PROFILE_SAVE_RESTORE) {
+                final long diff = System.currentTimeMillis() - now;
+                Log.w(TAG, "PROF: Write User HistoryDictionary: " + mLocale + ", "+ diff
+                        + "ms. Total: " + profTotal + ". Insert: " + profInsert + ". Delete: "
+                        + profDelete);
+            }
+            db.setTransactionSuccessful();
+            return null;
         }
 
         private static ContentValues getContentValues(String word1, String word2, String locale) {
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
index fa067f4..3dc4543 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
@@ -16,25 +16,27 @@
 
 package com.android.inputmethod.keyboard.internal;
 
-import android.test.AndroidTestCase;
+import android.app.Instrumentation;
+import android.test.InstrumentationTestCase;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Locale;
 
-public class KeySpecParserCsvTests extends AndroidTestCase {
+public class KeySpecParserCsvTests extends InstrumentationTestCase {
     private final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
+        final Instrumentation instrumentation = getInstrumentation();
         mTextsSet.setLanguage(Locale.ENGLISH.getLanguage());
-        mTextsSet.loadStringResources(getContext());
+        mTextsSet.loadStringResources(instrumentation.getTargetContext());
         final String[] testResourceNames = getAllResourceIdNames(
                 com.android.inputmethod.latin.tests.R.string.class);
-        mTextsSet.loadStringResourcesInternal(getTestContext(),
+        mTextsSet.loadStringResourcesInternal(instrumentation.getContext(),
                 testResourceNames,
                 com.android.inputmethod.latin.tests.R.string.empty_string);
     }