merge in jb-mr1-release history after reset to jb-mr1-dev
diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml
index 56642c4..57e9ff4 100644
--- a/java/res/values-hi/strings.xml
+++ b/java/res/values-hi/strings.xml
@@ -72,7 +72,7 @@
     <string name="label_to_alpha_key" msgid="4793983863798817523">"कखग"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?१२३"</string>
     <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"१२३"</string>
-    <string name="label_pause_key" msgid="181098308428035340">"रोकें"</string>
+    <string name="label_pause_key" msgid="181098308428035340">"पॉज़ करें"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"प्रतीक्षा करें"</string>
     <string name="spoken_use_headphones" msgid="896961781287283493">"ज़ोर से बोली गई पासवर्ड कुंजियां सुनने के लिए हेडसेट प्‍लग इन करें."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"वर्तमान पाठ %s है"</string>
@@ -116,7 +116,7 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"सहेजने के लिए पुन: स्‍पर्श करें"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"शब्‍दकोश उपलब्‍ध है"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"उपयोगकर्ता फ़ीडबैक सक्षम करें"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"उपयोग के आंकड़े और क्रैश रिपोर्ट Google को स्वचालित रूप से भेज कर इस इनपुट पद्धति संपादक को बेहतर बनाने में सहायता करें."</string>
+    <string name="prefs_description_log" msgid="5827825607258246003">"उपयोग के आंकड़े और क्रैश रिपोर्ट Google को अपने आप भेज कर इस इनपुट पद्धति संपादक को बेहतर बनाने में सहायता करें."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"कीबोर्ड थीम"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"अंग्रेज़ी (यूके)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"अंग्रेज़ी (यूएस)"</string>
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
index 70e38fd..039c77b 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
@@ -35,6 +35,7 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.latin.CollectionUtils;
 
 /**
  * Exposes a virtual view sub-tree for {@link KeyboardView} and generates
@@ -55,7 +56,7 @@
     private final AccessibilityUtils mAccessibilityUtils;
 
     /** A map of integer IDs to {@link Key}s. */
-    private final SparseArray<Key> mVirtualViewIdToKey = new SparseArray<Key>();
+    private final SparseArray<Key> mVirtualViewIdToKey = CollectionUtils.newSparseArray();
 
     /** Temporary rect used to calculate in-screen bounds. */
     private final Rect mTempBoundsInScreen = new Rect();
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 9b74070..5c45448 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -25,6 +25,7 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.R;
 
 import java.util.HashMap;
@@ -38,7 +39,7 @@
     private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper();
 
     // Map of key labels to spoken description resource IDs
-    private final HashMap<CharSequence, Integer> mKeyLabelMap;
+    private final HashMap<CharSequence, Integer> mKeyLabelMap = CollectionUtils.newHashMap();
 
     // Sparse array of spoken description resource IDs indexed by key codes
     private final SparseIntArray mKeyCodeMap;
@@ -52,7 +53,6 @@
     }
 
     private KeyCodeDescriptionMapper() {
-        mKeyLabelMap = new HashMap<CharSequence, Integer>();
         mKeyCodeMap = new SparseIntArray();
     }
 
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index 1183b5f..6ba309f 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
@@ -16,10 +16,6 @@
 
 package com.android.inputmethod.compat;
 
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
-
 import android.content.Context;
 import android.text.Spannable;
 import android.text.SpannableString;
@@ -27,6 +23,11 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
+
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
@@ -119,7 +120,7 @@
         } else {
             spannable = new SpannableString(pickedWord);
         }
-        final ArrayList<String> suggestionsList = new ArrayList<String>();
+        final ArrayList<String> suggestionsList = CollectionUtils.newArrayList();
         for (int i = 0; i < suggestedWords.size(); ++i) {
             if (suggestionsList.size() >= OBJ_SUGGESTIONS_MAX_SIZE) {
                 break;
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 489cc37..e37868b 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -33,6 +33,7 @@
 import com.android.inputmethod.keyboard.internal.KeyboardCodesSet;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.keyboard.internal.KeyboardTextsSet;
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
 import com.android.inputmethod.latin.R;
@@ -134,7 +135,7 @@
     public final Key[] mAltCodeKeysWhileTyping;
     public final KeyboardIconsSet mIconsSet;
 
-    private final SparseArray<Key> mKeyCache = new SparseArray<Key>();
+    private final SparseArray<Key> mKeyCache = CollectionUtils.newSparseArray();
 
     private final ProximityInfo mProximityInfo;
     private final boolean mProximityCharsCorrectionEnabled;
@@ -254,9 +255,9 @@
         public int GRID_WIDTH;
         public int GRID_HEIGHT;
 
-        public final HashSet<Key> mKeys = new HashSet<Key>();
-        public final ArrayList<Key> mShiftKeys = new ArrayList<Key>();
-        public final ArrayList<Key> mAltCodeKeysWhileTyping = new ArrayList<Key>();
+        public final HashSet<Key> mKeys = CollectionUtils.newHashSet();
+        public final ArrayList<Key> mShiftKeys = CollectionUtils.newArrayList();
+        public final ArrayList<Key> mAltCodeKeysWhileTyping = CollectionUtils.newArrayList();
         public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
         public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
         public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 64b3f09..76ac3de 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -36,6 +36,7 @@
 
 import com.android.inputmethod.compat.EditorInfoCompatUtils;
 import com.android.inputmethod.keyboard.KeyboardLayoutSet.Params.ElementParams;
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.InputAttributes;
 import com.android.inputmethod.latin.InputTypeUtils;
 import com.android.inputmethod.latin.LatinImeLogger;
@@ -71,7 +72,7 @@
     private final Params mParams;
 
     private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache =
-            new HashMap<KeyboardId, SoftReference<Keyboard>>();
+            CollectionUtils.newHashMap();
     private static final KeysCache sKeysCache = new KeysCache();
 
     public static class KeyboardLayoutSetException extends RuntimeException {
@@ -84,11 +85,7 @@
     }
 
     public static class KeysCache {
-        private final HashMap<Key, Key> mMap;
-
-        public KeysCache() {
-            mMap = new HashMap<Key, Key>();
-        }
+        private final HashMap<Key, Key> mMap = CollectionUtils.newHashMap();
 
         public void clear() {
             mMap.clear();
@@ -120,7 +117,7 @@
         int mWidth;
         // Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
         final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
-                new SparseArray<ElementParams>();
+                CollectionUtils.newSparseArray();
 
         static class ElementParams {
             int mKeyboardXmlId;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 0232fae..fd789f0 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -21,7 +21,6 @@
 import android.content.res.Resources;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
-import android.view.InflateException;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.inputmethod.EditorInfo;
@@ -38,7 +37,6 @@
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SettingsValues;
 import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.WordComposer;
 
 public class KeyboardSwitcher implements KeyboardState.SwitchActions {
@@ -47,24 +45,24 @@
     public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
 
     static class KeyboardTheme {
-        public final String mName;
         public final int mThemeId;
         public final int mStyleId;
 
-        public KeyboardTheme(String name, int themeId, int styleId) {
-            mName = name;
+        // Note: The themeId should be aligned with "themeId" attribute of Keyboard style
+        // in values/style.xml.
+        public KeyboardTheme(int themeId, int styleId) {
             mThemeId = themeId;
             mStyleId = styleId;
         }
     }
 
     private static final KeyboardTheme[] KEYBOARD_THEMES = {
-        new KeyboardTheme("Basic",            0, R.style.KeyboardTheme),
-        new KeyboardTheme("HighContrast",     1, R.style.KeyboardTheme_HighContrast),
-        new KeyboardTheme("Stone",            6, R.style.KeyboardTheme_Stone),
-        new KeyboardTheme("Stone.Bold",       7, R.style.KeyboardTheme_Stone_Bold),
-        new KeyboardTheme("GingerBread",      8, R.style.KeyboardTheme_Gingerbread),
-        new KeyboardTheme("IceCreamSandwich", 5, R.style.KeyboardTheme_IceCreamSandwich),
+        new KeyboardTheme(0, R.style.KeyboardTheme),
+        new KeyboardTheme(1, R.style.KeyboardTheme_HighContrast),
+        new KeyboardTheme(6, R.style.KeyboardTheme_Stone),
+        new KeyboardTheme(7, R.style.KeyboardTheme_Stone_Bold),
+        new KeyboardTheme(8, R.style.KeyboardTheme_Gingerbread),
+        new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich),
     };
 
     private SubtypeSwitcher mSubtypeSwitcher;
@@ -355,22 +353,9 @@
             mKeyboardView.closing();
         }
 
-        Utils.GCUtils.getInstance().reset();
-        boolean tryGC = true;
-        for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
-            try {
-                setContextThemeWrapper(mLatinIME, mKeyboardTheme);
-                mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
-                        R.layout.input_view, null);
-                tryGC = false;
-            } catch (OutOfMemoryError e) {
-                Log.w(TAG, "load keyboard failed: " + e);
-                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
-            } catch (InflateException e) {
-                Log.w(TAG, "load keyboard failed: " + e);
-                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
-            }
-        }
+        setContextThemeWrapper(mLatinIME, mKeyboardTheme);
+        mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
+                R.layout.input_view, null);
 
         mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
         if (isHardwareAcceleratedDrawingEnabled) {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index fe14c3f..0a70605 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -39,6 +39,7 @@
 import android.widget.TextView;
 
 import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
@@ -106,6 +107,7 @@
 
     // Key preview
     private final int mKeyPreviewLayoutId;
+    private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
     protected final KeyPreviewDrawParams mKeyPreviewDrawParams;
     private boolean mShowKeyPreviewPopup = true;
     private int mDelayAfterPreview;
@@ -117,7 +119,7 @@
     /** True if all keys should be drawn */
     private boolean mInvalidateAllKeys;
     /** The keys that should be drawn */
-    private final HashSet<Key> mInvalidatedKeys = new HashSet<Key>();
+    private final HashSet<Key> mInvalidatedKeys = CollectionUtils.newHashSet();
     /** The working rectangle variable */
     private final Rect mWorkingRect = new Rect();
     /** The keyboard bitmap buffer for faster updates */
@@ -129,9 +131,9 @@
     private final Paint mPaint = new Paint();
     private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
     // This sparse array caches key label text height in pixel indexed by key label text size.
-    private static final SparseArray<Float> sTextHeightCache = new SparseArray<Float>();
+    private static final SparseArray<Float> sTextHeightCache = CollectionUtils.newSparseArray();
     // This sparse array caches key label text width in pixel indexed by key label text size.
-    private static final SparseArray<Float> sTextWidthCache = new SparseArray<Float>();
+    private static final SparseArray<Float> sTextWidthCache = CollectionUtils.newSparseArray();
     private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
     private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
 
@@ -151,7 +153,10 @@
             final PointerTracker tracker = (PointerTracker) msg.obj;
             switch (msg.what) {
             case MSG_DISMISS_KEY_PREVIEW:
-                tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
+                final TextView previewText = keyboardView.mKeyPreviewTexts.get(tracker.mPointerId);
+                if (previewText != null) {
+                    previewText.setVisibility(INVISIBLE);
+                }
                 break;
             }
         }
@@ -164,7 +169,7 @@
             removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
         }
 
-        public void cancelAllDismissKeyPreviews() {
+        private void cancelAllDismissKeyPreviews() {
             removeMessages(MSG_DISMISS_KEY_PREVIEW);
         }
 
@@ -906,15 +911,30 @@
         }
     }
 
-    // Called by {@link PointerTracker} constructor to create a TextView.
-    @Override
-    public TextView inflateKeyPreviewText() {
+    private TextView getKeyPreviewText(final int pointerId) {
+        TextView previewText = mKeyPreviewTexts.get(pointerId);
+        if (previewText != null) {
+            return previewText;
+        }
         final Context context = getContext();
         if (mKeyPreviewLayoutId != 0) {
-            return (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
+            previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
         } else {
-            return new TextView(context);
+            previewText = new TextView(context);
         }
+        mKeyPreviewTexts.put(pointerId, previewText);
+        return previewText;
+    }
+
+    private void dismissAllKeyPreviews() {
+        final int pointerCount = mKeyPreviewTexts.size();
+        for (int id = 0; id < pointerCount; id++) {
+            final TextView previewText = mKeyPreviewTexts.get(id);
+            if (previewText != null) {
+                previewText.setVisibility(INVISIBLE);
+            }
+        }
+        PointerTracker.setReleasedKeyGraphicsToAllKeys();
     }
 
     @Override
@@ -970,7 +990,7 @@
     public void showKeyPreview(PointerTracker tracker) {
         if (!mShowKeyPreviewPopup) return;
 
-        final TextView previewText = tracker.getKeyPreviewText();
+        final TextView previewText = getKeyPreviewText(tracker.mPointerId);
         // If the key preview has no parent view yet, add it to the ViewGroup which can place
         // key preview absolutely in SoftInputWindow.
         if (previewText.getParent() == null) {
@@ -1081,7 +1101,7 @@
     }
 
     public void closing() {
-        PointerTracker.dismissAllKeyPreviews();
+        dismissAllKeyPreviews();
         cancelAllMessages();
 
         mInvalidateAllKeys = true;
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index f572cb8..1b309a4 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -528,7 +528,17 @@
         // to properly show the splash screen, which requires that the window token of the
         // KeyboardView be non-null.
         if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow();
+            ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this);
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        // Notify the research logger that the keyboard view has been detached.  This is needed
+        // to invalidate the reference of {@link MainKeyboardView} to null.
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow();
         }
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 7d565a6..d9a7cb4 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -21,12 +21,11 @@
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.MotionEvent;
-import android.view.View;
-import android.widget.TextView;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
 import com.android.inputmethod.keyboard.internal.GestureStroke;
 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.InputPointers;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.define.ProductionFlag;
@@ -78,7 +77,6 @@
 
     public interface DrawingProxy extends MoreKeysPanel.Controller {
         public void invalidateKey(Key key);
-        public TextView inflateKeyPreviewText();
         public void showKeyPreview(PointerTracker tracker);
         public void dismissKeyPreview(PointerTracker tracker);
         public void showGestureTrail(PointerTracker tracker);
@@ -125,7 +123,7 @@
     private static int sTouchNoiseThresholdDistanceSquared;
     private static boolean sNeedsPhantomSuddenMoveEventHack;
 
-    private static final ArrayList<PointerTracker> sTrackers = new ArrayList<PointerTracker>();
+    private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList();
     private static final InputPointers sAggregratedPointers = new InputPointers(
             GestureStroke.DEFAULT_CAPACITY);
     private static PointerTrackerQueue sPointerTrackerQueue;
@@ -139,7 +137,6 @@
 
     private Keyboard mKeyboard;
     private int mKeyQuarterWidthSquared;
-    private final TextView mKeyPreviewText;
 
     private boolean mIsAlphabetKeyboard;
     private boolean mIsPossibleGesture = false;
@@ -260,11 +257,10 @@
         updateGestureHandlingMode();
     }
 
-    public static void dismissAllKeyPreviews() {
+    public static void setReleasedKeyGraphicsToAllKeys() {
         final int trackersSize = sTrackers.size();
         for (int i = 0; i < trackersSize; ++i) {
             final PointerTracker tracker = sTrackers.get(i);
-            tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
             tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
         }
     }
@@ -311,11 +307,6 @@
         mListener = handler.getKeyboardActionListener();
         mDrawingProxy = handler.getDrawingProxy();
         mTimerProxy = handler.getTimerProxy();
-        mKeyPreviewText = mDrawingProxy.inflateKeyPreviewText();
-    }
-
-    public TextView getKeyPreviewText() {
-        return mKeyPreviewText;
     }
 
     // Returns true if keyboard has been changed by this callback.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index 94a7b82..13214bb 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -21,6 +21,7 @@
 import android.text.TextUtils;
 
 import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.StringUtils;
 
@@ -258,7 +259,7 @@
             throw new IllegalArgumentException();
         }
 
-        final ArrayList<T> list = new ArrayList<T>(end - start);
+        final ArrayList<T> list = CollectionUtils.newArrayList(end - start);
         for (int i = start; i < end; i++) {
             list.add(array[i]);
         }
@@ -438,7 +439,7 @@
                 // Skip empty entry.
                 if (pos - start > 0) {
                     if (list == null) {
-                        list = new ArrayList<String>();
+                        list = CollectionUtils.newArrayList();
                     }
                     list.add(text.substring(start, pos));
                 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
index 291b3b9..e40cf45 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
@@ -21,6 +21,7 @@
 import android.util.SparseArray;
 
 import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.XmlParseUtils;
 
@@ -33,7 +34,7 @@
     private static final String TAG = KeyStyles.class.getSimpleName();
     private static final boolean DEBUG = false;
 
-    final HashMap<String, KeyStyle> mStyles = new HashMap<String, KeyStyle>();
+    final HashMap<String, KeyStyle> mStyles = CollectionUtils.newHashMap();
 
     final KeyboardTextsSet mTextsSet;
     private final KeyStyle mEmptyKeyStyle;
@@ -90,7 +91,7 @@
 
     private class DeclaredKeyStyle extends KeyStyle {
         private final String mParentStyleName;
-        private final SparseArray<Object> mStyleAttributes = new SparseArray<Object>();
+        private final SparseArray<Object> mStyleAttributes = CollectionUtils.newSparseArray();
 
         public DeclaredKeyStyle(String parentStyleName) {
             mParentStyleName = parentStyleName;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
index f7981a3..f7923d0 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
@@ -17,13 +17,13 @@
 package com.android.inputmethod.keyboard.internal;
 
 import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.CollectionUtils;
 
 import java.util.HashMap;
 
 public class KeyboardCodesSet {
-    private static final HashMap<String, int[]> sLanguageToCodesMap =
-            new HashMap<String, int[]>();
-    private static final HashMap<String, Integer> sNameToIdMap = new HashMap<String, Integer>();
+    private static final HashMap<String, int[]> sLanguageToCodesMap = CollectionUtils.newHashMap();
+    private static final HashMap<String, Integer> sNameToIdMap = CollectionUtils.newHashMap();
 
     private int[] mCodes = DEFAULT;
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 5155851..4a98a36 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -22,6 +22,7 @@
 import android.util.Log;
 import android.util.SparseIntArray;
 
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.R;
 
 import java.util.HashMap;
@@ -35,7 +36,7 @@
     private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray();
 
     // Icon name to icon id map.
-    private static final HashMap<String, Integer> sNameToIdsMap = new HashMap<String, Integer>();
+    private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
 
     private static final Object[] NAMES_AND_ATTR_IDS = {
         "undefined",                    ATTR_UNDEFINED,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index bec0f1f..a608cde 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.R;
 
 import java.util.HashMap;
@@ -45,14 +46,12 @@
  */
 public final class KeyboardTextsSet {
     // Language to texts map.
-    private static final HashMap<String, String[]> sLocaleToTextsMap =
-            new HashMap<String, String[]>();
-    private static final HashMap<String, Integer> sNameToIdsMap =
-            new HashMap<String, Integer>();
+    private static final HashMap<String, String[]> sLocaleToTextsMap = CollectionUtils.newHashMap();
+    private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
 
     private String[] mTexts;
     // Resource name to text map.
-    private HashMap<String, String> mResourceNameToTextsMap = new HashMap<String, String>();
+    private HashMap<String, String> mResourceNameToTextsMap = CollectionUtils.newHashMap();
 
     public void setLanguage(final String language) {
         mTexts = sLocaleToTextsMap.get(language);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index 1c7ceaf..e0858c0 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -18,6 +18,8 @@
 
 import android.util.Log;
 
+import com.android.inputmethod.latin.CollectionUtils;
+
 import java.util.ArrayList;
 
 public class PointerTrackerQueue {
@@ -32,7 +34,7 @@
 
     private static final int INITIAL_CAPACITY = 10;
     private final ArrayList<Element> mExpandableArrayOfActivePointers =
-            new ArrayList<Element>(INITIAL_CAPACITY);
+            CollectionUtils.newArrayList(INITIAL_CAPACITY);
     private int mArraySize = 0;
 
     public synchronized int size() {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index 59a92d6..3f33aee 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -28,6 +28,7 @@
 import android.widget.RelativeLayout;
 
 import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
 
@@ -47,7 +48,7 @@
     private int mXOrigin;
     private int mYOrigin;
 
-    private final SparseArray<PointerTracker> mPointers = new SparseArray<PointerTracker>();
+    private final SparseArray<PointerTracker> mPointers = CollectionUtils.newSparseArray();
 
     private String mGestureFloatingPreviewText;
     private int mLastPointerX;
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
index f8f1395..4b47a26 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
@@ -91,7 +91,7 @@
         }
         final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR);
         final ArrayList<InputMethodSubtype> subtypesList =
-                new ArrayList<InputMethodSubtype>(prefSubtypeArray.length);
+                CollectionUtils.newArrayList(prefSubtypeArray.length);
         for (final String prefSubtype : prefSubtypeArray) {
             final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype);
             if (subtype.getNameResId() == SubtypeLocale.UNKNOWN_KEYBOARD_LAYOUT) {
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
index 779a388..d01592a 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
@@ -89,7 +89,7 @@
             super(context, android.R.layout.simple_spinner_item);
             setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
 
-            final TreeSet<SubtypeLocaleItem> items = new TreeSet<SubtypeLocaleItem>();
+            final TreeSet<SubtypeLocaleItem> items = CollectionUtils.newTreeSet();
             final InputMethodInfo imi = ImfUtils.getInputMethodInfoOfThisIme(context);
             final int count = imi.getSubtypeCount();
             for (int i = 0; i < count; i++) {
@@ -533,7 +533,7 @@
 
     private InputMethodSubtype[] getSubtypes() {
         final PreferenceGroup group = getPreferenceScreen();
-        final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+        final ArrayList<InputMethodSubtype> subtypes = CollectionUtils.newArrayList();
         final int count = group.getPreferenceCount();
         for (int i = 0; i < count; i++) {
             final Preference pref = group.getPreference(i);
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index f663584..8909526 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -63,7 +63,7 @@
     private final boolean mUseFullEditDistance;
 
     private final SparseArray<DicTraverseSession> mDicTraverseSessions =
-            new SparseArray<DicTraverseSession>();
+            CollectionUtils.newSparseArray();
 
     // TODO: There should be a way to remove used DicTraverseSession objects from
     // {@code mDicTraverseSessions}.
@@ -160,7 +160,7 @@
                 mUseFullEditDistance, mOutputChars, mOutputScores, mSpaceIndices, mOutputTypes);
         final int count = Math.min(tmpCount, MAX_PREDICTIONS);
 
-        final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
+        final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
         for (int j = 0; j < count; ++j) {
             if (composerSize > 0 && mOutputScores[j] < 1) break;
             final int start = j * MAX_WORD_LENGTH;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 236c198..799aea8 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -99,7 +99,7 @@
         }
 
         try {
-            final List<WordListInfo> list = new ArrayList<WordListInfo>();
+            final List<WordListInfo> list = CollectionUtils.newArrayList();
             do {
                 final String wordListId = c.getString(0);
                 final String wordListLocale = c.getString(1);
@@ -267,7 +267,7 @@
         final ContentResolver resolver = context.getContentResolver();
         final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
                 hasDefaultWordList);
-        final List<AssetFileAddress> fileAddressList = new ArrayList<AssetFileAddress>();
+        final List<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList();
         for (WordListInfo id : idList) {
             final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, resolver, context);
             if (null != afd) {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index dd11aaa..4ada909 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -260,8 +260,7 @@
             final Context context) {
         final File[] directoryList = getCachedDirectoryList(context);
         if (null == directoryList) return EMPTY_FILE_ARRAY;
-        final HashMap<String, FileAndMatchLevel> cacheFiles =
-                new HashMap<String, FileAndMatchLevel>();
+        final HashMap<String, FileAndMatchLevel> cacheFiles = CollectionUtils.newHashMap();
         for (File directory : directoryList) {
             if (!directory.isDirectory()) continue;
             final String dirLocale = getWordListIdFromFileName(directory.getName());
@@ -359,7 +358,7 @@
             }
             final int formatVersion = raf.readInt();
             final int headerSize = raf.readInt();
-            final HashMap<String, String> options = new HashMap<String, String>();
+            final HashMap<String, String> options = CollectionUtils.newHashMap();
             BinaryDictInputOutput.populateOptionsFromFile(raf, headerSize, options);
             final String version = options.get(VERSION_KEY);
             if (null == version) {
@@ -404,7 +403,7 @@
         final DictPackSettings dictPackSettings = new DictPackSettings(context);
 
         boolean foundMainDict = false;
-        final ArrayList<AssetFileAddress> fileList = new ArrayList<AssetFileAddress>();
+        final ArrayList<AssetFileAddress> fileList = CollectionUtils.newArrayList();
         // cachedWordLists may not be null, see doc for getCachedDictionaryList
         for (final File f : cachedWordLists) {
             final String wordListId = getWordListIdFromFileName(f.getName());
diff --git a/java/src/com/android/inputmethod/latin/CollectionUtils.java b/java/src/com/android/inputmethod/latin/CollectionUtils.java
new file mode 100644
index 0000000..baa2ee1
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/CollectionUtils.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2012 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.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class CollectionUtils {
+    private CollectionUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static <K,V> HashMap<K,V> newHashMap() {
+        return new HashMap<K,V>();
+    }
+
+    public static <K,V> TreeMap<K,V> newTreeMap() {
+        return new TreeMap<K,V>();
+    }
+
+    public static <K, V> Map<K,V> newSynchronizedTreeMap() {
+        final TreeMap<K,V> treeMap = newTreeMap();
+        return Collections.synchronizedMap(treeMap);
+    }
+
+    public static <K,V> ConcurrentHashMap<K,V> newConcurrentHashMap() {
+        return new ConcurrentHashMap<K,V>();
+    }
+
+    public static <E> HashSet<E> newHashSet() {
+        return new HashSet<E>();
+    }
+
+    public static <E> TreeSet<E> newTreeSet() {
+        return new TreeSet<E>();
+    }
+
+    public static <E> ArrayList<E> newArrayList() {
+        return new ArrayList<E>();
+    }
+
+    public static <E> ArrayList<E> newArrayList(final int initialCapacity) {
+        return new ArrayList<E>(initialCapacity);
+    }
+
+    public static <E> ArrayList<E> newArrayList(final Collection<E> collection) {
+        return new ArrayList<E>(collection);
+    }
+
+    public static <E> LinkedList<E> newLinkedList() {
+        return new LinkedList<E>();
+    }
+
+    public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList() {
+        return new CopyOnWriteArrayList<E>();
+    }
+
+    public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList(
+            final Collection<E> collection) {
+        return new CopyOnWriteArrayList<E>(collection);
+    }
+
+    public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList(final E[] array) {
+        return new CopyOnWriteArrayList<E>(array);
+    }
+
+    public static <E> SparseArray<E> newSparseArray() {
+        return new SparseArray<E>();
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index ee80f25..4acab6b 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -35,22 +35,22 @@
 
     public DictionaryCollection(final String dictType) {
         super(dictType);
-        mDictionaries = new CopyOnWriteArrayList<Dictionary>();
+        mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
     }
 
     public DictionaryCollection(final String dictType, Dictionary... dictionaries) {
         super(dictType);
         if (null == dictionaries) {
-            mDictionaries = new CopyOnWriteArrayList<Dictionary>();
+            mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
         } else {
-            mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+            mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
             mDictionaries.removeAll(Collections.singleton(null));
         }
     }
 
     public DictionaryCollection(final String dictType, Collection<Dictionary> dictionaries) {
         super(dictType);
-        mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+        mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
         mDictionaries.removeAll(Collections.singleton(null));
     }
 
@@ -63,7 +63,7 @@
         // dictionary and add the rest to it if not null, hence the get(0)
         ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer,
                 prevWord, proximityInfo);
-        if (null == suggestions) suggestions = new ArrayList<SuggestedWordInfo>();
+        if (null == suggestions) suggestions = CollectionUtils.newArrayList();
         final int length = dictionaries.size();
         for (int i = 1; i < length; ++ i) {
             final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 06a5f4b..cdd01d0 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -53,7 +53,7 @@
                     createBinaryDictionary(context, locale));
         }
 
-        final LinkedList<Dictionary> dictList = new LinkedList<Dictionary>();
+        final LinkedList<Dictionary> dictList = CollectionUtils.newLinkedList();
         final ArrayList<AssetFileAddress> assetFileList =
                 BinaryDictionaryGetter.getDictionaryFiles(locale, context);
         if (null != assetFileList) {
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 016530a..cdf5247 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -62,7 +62,7 @@
      * that filename.
      */
     private static final HashMap<String, DictionaryController> sSharedDictionaryControllers =
-            new HashMap<String, DictionaryController>();
+            CollectionUtils.newHashMap();
 
     /** The application context. */
     protected final Context mContext;
@@ -159,9 +159,9 @@
      * the native side.
      */
     public void clearFusionDictionary() {
+        final HashMap<String, String> attributes = CollectionUtils.newHashMap();
         mFusionDictionary = new FusionDictionary(new Node(),
-                new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false, 
-                        false));
+                new FusionDictionary.DictionaryOptions(attributes, false, false));
     }
 
     /**
@@ -175,7 +175,7 @@
             mFusionDictionary.add(word, frequency, null);
         } else {
             // TODO: Do this in the subclass, with this class taking an arraylist.
-            final ArrayList<WeightedString> shortcutTargets = new ArrayList<WeightedString>();
+            final ArrayList<WeightedString> shortcutTargets = CollectionUtils.newArrayList();
             shortcutTargets.add(new WeightedString(shortcutTarget, frequency));
             mFusionDictionary.add(word, frequency, shortcutTargets);
         }
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 11dca34..8a38d1e 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -230,7 +230,7 @@
             childNode.mTerminal = true;
             if (isShortcutOnly) {
                 if (null == childNode.mShortcutTargets) {
-                    childNode.mShortcutTargets = new ArrayList<char[]>();
+                    childNode.mShortcutTargets = CollectionUtils.newArrayList();
                 }
                 childNode.mShortcutTargets.add(shortcutTarget.toCharArray());
             } else {
@@ -250,7 +250,7 @@
     public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
             final CharSequence prevWord, final ProximityInfo proximityInfo) {
         if (reloadDictionaryIfRequired()) return null;
-        if (composer.size() <= 1) {
+        if (composer.size() > 1) {
             if (composer.size() >= BinaryDictionary.MAX_WORD_LENGTH) {
                 return null;
             }
@@ -259,7 +259,7 @@
             return suggestions;
         } else {
             if (TextUtils.isEmpty(prevWord)) return null;
-            final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
+            final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
             runBigramReverseLookUp(prevWord, suggestions);
             return suggestions;
         }
@@ -278,7 +278,7 @@
 
     protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
             final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
-        final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
+        final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
         mInputLength = codes.size();
         if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
         final InputPointers ips = codes.getInputPointers();
@@ -550,7 +550,7 @@
         Node secondWord = searchWord(mRoots, word2, 0, null);
         LinkedList<NextWord> bigrams = firstWord.mNGrams;
         if (bigrams == null || bigrams.size() == 0) {
-            firstWord.mNGrams = new LinkedList<NextWord>();
+            firstWord.mNGrams = CollectionUtils.newLinkedList();
             bigrams = firstWord.mNGrams;
         } else {
             for (NextWord nw : bigrams) {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index f2ebc97..4d5f93b 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -361,7 +361,7 @@
         mPrefs = prefs;
         LatinImeLogger.init(this, prefs);
         if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.getInstance().init(this, prefs, mKeyboardSwitcher);
+            ResearchLogger.getInstance().init(this, prefs);
         }
         InputMethodManagerCompatWrapper.init(this);
         SubtypeSwitcher.init(this);
@@ -381,18 +381,7 @@
 
         ImfUtils.setAdditionalInputMethodSubtypes(this, mCurrentSettings.getAdditionalSubtypes());
 
-        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();
-                tryGC = false;
-            } catch (OutOfMemoryError e) {
-                tryGC = Utils.GCUtils.getInstance().tryGCOrWait("InitSuggest", e);
-            }
-        }
+        initSuggest();
 
         mDisplayOrientation = res.getConfiguration().orientation;
 
@@ -416,7 +405,8 @@
     }
 
     // Has to be package-visible for unit tests
-    /* package */ void loadSettings() {
+    /* package for test */
+    void loadSettings() {
         // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
         // is not guaranteed. It may even be called at the same time on a different thread.
         if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
@@ -540,7 +530,10 @@
 
     @Override
     public void onConfigurationChanged(Configuration conf) {
-        mSubtypeSwitcher.onConfigurationChanged(conf);
+        // System locale has been changed. Needs to reload keyboard.
+        if (mSubtypeSwitcher.onConfigurationChanged(conf, this)) {
+            loadKeyboard();
+        }
         // If orientation changed while predicting, commit the change
         if (mDisplayOrientation != conf.orientation) {
             mDisplayOrientation = conf.orientation;
@@ -607,6 +600,7 @@
         // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
         // is not guaranteed. It may even be called at the same time on a different thread.
         mSubtypeSwitcher.updateSubtype(subtype);
+        loadKeyboard();
     }
 
     private void onStartInputInternal(EditorInfo editorInfo, boolean restarting) {
@@ -675,7 +669,15 @@
         final boolean inputTypeChanged = !mCurrentSettings.isSameInputType(editorInfo);
         final boolean isDifferentTextField = !restarting || inputTypeChanged;
         if (isDifferentTextField) {
-            mSubtypeSwitcher.updateParametersOnStartInputView();
+            final boolean currentSubtypeEnabled = mSubtypeSwitcher
+                    .updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled();
+            if (!currentSubtypeEnabled) {
+                // Current subtype is disabled. Needs to update subtype and keyboard.
+                final InputMethodSubtype newSubtype = ImfUtils.getCurrentInputMethodSubtype(
+                        this, mSubtypeSwitcher.getNoLanguageSubtype());
+                mSubtypeSwitcher.updateSubtype(newSubtype);
+                loadKeyboard();
+            }
         }
 
         // The EditorInfo might have a flag that affects fullscreen mode.
@@ -907,13 +909,13 @@
                 }
             }
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
-        }
         if (!mCurrentSettings.isApplicationSpecifiedCompletionsOn()) return;
         mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
         if (applicationSpecifiedCompletions == null) {
             clearSuggestionStrip();
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.latinIME_onDisplayCompletions(null);
+            }
             return;
         }
 
@@ -935,6 +937,9 @@
         // this case? This says to keep whatever the user typed.
         mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
         setSuggestionStripShown(true);
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
+        }
     }
 
     private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) {
@@ -1055,9 +1060,6 @@
         final CharSequence typedWord = mWordComposer.getTypedWord();
         if (typedWord.length() > 0) {
             mConnection.commitText(typedWord, 1);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_commitText(typedWord);
-            }
             final CharSequence prevWord = addToUserHistoryDictionary(typedWord);
             mLastComposedWord = mWordComposer.commitWord(
                     LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(),
@@ -1111,12 +1113,9 @@
         if (lastTwo != null && lastTwo.length() == 2
                 && lastTwo.charAt(0) == Keyboard.CODE_SPACE) {
             mConnection.deleteSurroundingText(2, 0);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_deleteSurroundingText(2);
-            }
             mConnection.commitText(lastTwo.charAt(1) + " ", 1);
             if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_swapSwapperAndSpaceWhileInBatchEdit();
+                ResearchLogger.latinIME_swapSwapperAndSpace();
             }
             mKeyboardSwitcher.updateShiftState();
         }
@@ -1133,9 +1132,6 @@
             mHandler.cancelDoubleSpacesTimer();
             mConnection.deleteSurroundingText(2, 0);
             mConnection.commitText(". ", 1);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_doubleSpaceAutoPeriod();
-            }
             mKeyboardSwitcher.updateShiftState();
             return true;
         }
@@ -1199,9 +1195,6 @@
 
     private void performEditorAction(int actionId) {
         mConnection.performEditorAction(actionId);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_performEditorAction(actionId);
-        }
     }
 
     private void handleLanguageSwitchKey() {
@@ -1238,6 +1231,9 @@
         // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
         if (code >= '0' && code <= '9') {
             super.sendKeyChar((char)code);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.latinIME_sendKeyCodePoint(code);
+            }
             return;
         }
 
@@ -1254,9 +1250,6 @@
             final String text = new String(new int[] { code }, 0, 1);
             mConnection.commitText(text, text.length());
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_sendKeyCodePoint(code);
-        }
     }
 
     // Implementation of {@link KeyboardActionListener}.
@@ -1305,7 +1298,7 @@
             onSettingsKeyPressed();
             break;
         case Keyboard.CODE_SHORTCUT:
-            mSubtypeSwitcher.switchToShortcutIME();
+            mSubtypeSwitcher.switchToShortcutIME(this);
             break;
         case Keyboard.CODE_ACTION_ENTER:
             performEditorAction(getActionId(switcher.getKeyboard()));
@@ -1367,9 +1360,6 @@
             sendKeyCodePoint(Keyboard.CODE_SPACE);
         }
         mConnection.commitText(text, 1);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_commitText(text);
-        }
         mConnection.endBatchEdit();
         mKeyboardSwitcher.updateShiftState();
         mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT);
@@ -1467,9 +1457,6 @@
             // like the smiley key or the .com key.
             final int length = mEnteredText.length();
             mConnection.deleteSurroundingText(length, 0);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_deleteSurroundingText(length);
-            }
             // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
             // In addition we know that spaceState is false, and that we should not be
             // reverting any autocorrect at this point. So we can safely return.
@@ -1489,9 +1476,6 @@
                 mHandler.postUpdateSuggestionStrip();
             } else {
                 mConnection.deleteSurroundingText(1, 0);
-                if (ProductionFlag.IS_EXPERIMENTAL) {
-                    ResearchLogger.latinIME_deleteSurroundingText(1);
-                }
             }
         } else {
             if (mLastComposedWord.canRevertCommit()) {
@@ -1520,9 +1504,6 @@
                 final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart;
                 mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
                 mConnection.deleteSurroundingText(lengthToDelete, 0);
-                if (ProductionFlag.IS_EXPERIMENTAL) {
-                    ResearchLogger.latinIME_deleteSurroundingText(lengthToDelete);
-                }
             } else {
                 // There is no selection, just delete one character.
                 if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
@@ -1541,14 +1522,8 @@
                 } else {
                     mConnection.deleteSurroundingText(1, 0);
                 }
-                if (ProductionFlag.IS_EXPERIMENTAL) {
-                    ResearchLogger.latinIME_deleteSurroundingText(1);
-                }
                 if (mDeleteCount > DELETE_ACCELERATE_AT) {
                     mConnection.deleteSurroundingText(1, 0);
-                    if (ProductionFlag.IS_EXPERIMENTAL) {
-                        ResearchLogger.latinIME_deleteSurroundingText(1);
-                    }
                 }
             }
             if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) {
@@ -1727,7 +1702,8 @@
 
     // TODO: make this private
     // Outside LatinIME, only used by the test suite.
-    /* package for tests */ boolean isShowingPunctuationList() {
+    /* package for tests */
+    boolean isShowingPunctuationList() {
         if (mSuggestionStripView == null) return false;
         return mCurrentSettings.mSuggestPuncList == mSuggestionStripView.getSuggestions();
     }
@@ -1873,10 +1849,6 @@
                         + "is empty? Impossible! I must commit suicide.");
             }
             Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorCodePoint);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_commitCurrentAutoCorrection(typedWord,
-                        autoCorrection.toString());
-            }
             mExpectingUpdateSelection = true;
             commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
                     separatorCodePoint);
@@ -1901,12 +1873,12 @@
             // So, LatinImeLogger logs "" as a user's input.
             LatinImeLogger.logOnManualSuggestion("", suggestion.toString(), index, suggestedWords);
             // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_punctuationSuggestion(index, suggestion);
-            }
             final int primaryCode = suggestion.charAt(0);
             onCodeInput(primaryCode,
                     Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.latinIME_punctuationSuggestion(index, suggestion);
+            }
             return;
         }
 
@@ -1933,10 +1905,6 @@
             final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
             mConnection.commitCompletion(completionInfo);
             mConnection.endBatchEdit();
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_pickApplicationSpecifiedCompletion(
-                        index, completionInfo.getText());
-            }
             return;
         }
 
@@ -1945,12 +1913,12 @@
         final String replacedWord = mWordComposer.getTypedWord().toString();
         LatinImeLogger.logOnManualSuggestion(replacedWord,
                 suggestion.toString(), index, suggestedWords);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion);
-        }
         mExpectingUpdateSelection = true;
         commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
                 LastComposedWord.NOT_A_SEPARATOR);
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion);
+        }
         mConnection.endBatchEdit();
         // Don't allow cancellation of manual pick
         mLastComposedWord.deactivate();
@@ -1985,9 +1953,6 @@
         final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
         mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
                 this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_commitText(chosenWord);
-        }
         // Add the word to the user history dictionary
         final CharSequence prevWord = addToUserHistoryDictionary(chosenWord);
         // TODO: figure out here if this is an auto-correct or if the best word is actually
@@ -2055,9 +2020,6 @@
         mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
         final int length = word.length();
         mConnection.deleteSurroundingText(length, 0);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_deleteSurroundingText(length);
-        }
         mConnection.setComposingText(word, 1);
         mHandler.postUpdateSuggestionStrip();
     }
@@ -2085,9 +2047,6 @@
             }
         }
         mConnection.deleteSurroundingText(deleteLength, 0);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_deleteSurroundingText(deleteLength);
-        }
         if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
             mUserHistoryDictionary.cancelAddingUserHistory(
                     previousWord.toString(), committedWord.toString());
@@ -2112,9 +2071,10 @@
         return mCurrentSettings.isWordSeparator(code);
     }
 
-    // Notify that language or mode have been changed and toggleLanguage will update KeyboardID
-    // according to new language or mode. Called from SubtypeSwitcher.
-    public void onRefreshKeyboard() {
+    // TODO: Make this private
+    // Outside LatinIME, only used by the {@link InputTestsBase} test suite.
+    /* package for test */
+    void loadKeyboard() {
         // When the device locale is changed in SetupWizard etc., this method may get called via
         // onConfigurationChanged before SoftInputWindow is shown.
         initSuggest();
diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/LocaleUtils.java
index b938dd3..3b08cab 100644
--- a/java/src/com/android/inputmethod/latin/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/LocaleUtils.java
@@ -193,7 +193,7 @@
         }
     }
 
-    private static final HashMap<String, Locale> sLocaleCache = new HashMap<String, Locale>();
+    private static final HashMap<String, Locale> sLocaleCache = CollectionUtils.newHashMap();
 
     /**
      * Creates a locale from a string specification.
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 8b4c173..41e59e9 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -55,7 +55,9 @@
     public void beginBatchEdit() {
         if (++mNestLevel == 1) {
             mIC = mParent.getCurrentInputConnection();
-            if (null != mIC) mIC.beginBatchEdit();
+            if (null != mIC) {
+                mIC.beginBatchEdit();
+            }
         } else {
             if (DBG) {
                 throw new RuntimeException("Nest level too deep");
@@ -66,7 +68,9 @@
     }
     public void endBatchEdit() {
         if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead
-        if (--mNestLevel == 0 && null != mIC) mIC.endBatchEdit();
+        if (--mNestLevel == 0 && null != mIC) {
+            mIC.endBatchEdit();
+        }
     }
 
     private void checkBatchEdit() {
@@ -79,12 +83,22 @@
 
     public void finishComposingText() {
         checkBatchEdit();
-        if (null != mIC) mIC.finishComposingText();
+        if (null != mIC) {
+            mIC.finishComposingText();
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_finishComposingText();
+            }
+        }
     }
 
     public void commitText(final CharSequence text, final int i) {
         checkBatchEdit();
-        if (null != mIC) mIC.commitText(text, i);
+        if (null != mIC) {
+            mIC.commitText(text, i);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_commitText(text, i);
+            }
+        }
     }
 
     public int getCursorCapsMode(final int inputType) {
@@ -107,37 +121,72 @@
 
     public void deleteSurroundingText(final int i, final int j) {
         checkBatchEdit();
-        if (null != mIC) mIC.deleteSurroundingText(i, j);
+        if (null != mIC) {
+            mIC.deleteSurroundingText(i, j);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_deleteSurroundingText(i, j);
+            }
+        }
     }
 
     public void performEditorAction(final int actionId) {
         mIC = mParent.getCurrentInputConnection();
-        if (null != mIC) mIC.performEditorAction(actionId);
+        if (null != mIC) {
+            mIC.performEditorAction(actionId);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_performEditorAction(actionId);
+            }
+        }
     }
 
     public void sendKeyEvent(final KeyEvent keyEvent) {
         checkBatchEdit();
-        if (null != mIC) mIC.sendKeyEvent(keyEvent);
+        if (null != mIC) {
+            mIC.sendKeyEvent(keyEvent);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_sendKeyEvent(keyEvent);
+            }
+        }
     }
 
     public void setComposingText(final CharSequence text, final int i) {
         checkBatchEdit();
-        if (null != mIC) mIC.setComposingText(text, i);
+        if (null != mIC) {
+            mIC.setComposingText(text, i);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_setComposingText(text, i);
+            }
+        }
     }
 
     public void setSelection(final int from, final int to) {
         checkBatchEdit();
-        if (null != mIC) mIC.setSelection(from, to);
+        if (null != mIC) {
+            mIC.setSelection(from, to);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_setSelection(from, to);
+            }
+        }
     }
 
     public void commitCorrection(final CorrectionInfo correctionInfo) {
         checkBatchEdit();
-        if (null != mIC) mIC.commitCorrection(correctionInfo);
+        if (null != mIC) {
+            mIC.commitCorrection(correctionInfo);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_commitCorrection(correctionInfo);
+            }
+        }
     }
 
     public void commitCompletion(final CompletionInfo completionInfo) {
         checkBatchEdit();
-        if (null != mIC) mIC.commitCompletion(completionInfo);
+        if (null != mIC) {
+            mIC.commitCompletion(completionInfo);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_commitCompletion(completionInfo);
+            }
+        }
     }
 
     public CharSequence getNthPreviousWord(final String sentenceSeperators, final int n) {
@@ -315,9 +364,6 @@
         if (lastOne != null && lastOne.length() == 1
                 && lastOne.charAt(0) == Keyboard.CODE_SPACE) {
             deleteSurroundingText(1, 0);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_deleteSurroundingText(1);
-            }
         }
     }
 
@@ -382,13 +428,7 @@
             return false;
         }
         deleteSurroundingText(2, 0);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_deleteSurroundingText(2);
-        }
         commitText("  ", 1);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_revertDoubleSpaceWhileInBatchEdit();
-        }
         return true;
     }
 
@@ -409,13 +449,7 @@
             return false;
         }
         deleteSurroundingText(2, 0);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_deleteSurroundingText(2);
-        }
         commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_revertSwapPunctuation();
-        }
         return true;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index c8755be..dcd2532 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -185,7 +185,7 @@
 
     // Helper functions to create member values.
     private static SuggestedWords createSuggestPuncList(final String[] puncs) {
-        final ArrayList<SuggestedWordInfo> puncList = new ArrayList<SuggestedWordInfo>();
+        final ArrayList<SuggestedWordInfo> puncList = CollectionUtils.newArrayList();
         if (puncs != null) {
             for (final String puncSpec : puncs) {
                 puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec),
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 6e7d985..39c59b4 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -53,7 +53,7 @@
         if (TextUtils.isEmpty(csv)) return "";
         final String[] elements = csv.split(",");
         if (!containsInArray(key, elements)) return csv;
-        final ArrayList<String> result = new ArrayList<String>(elements.length - 1);
+        final ArrayList<String> result = CollectionUtils.newArrayList(elements.length - 1);
         for (final String element : elements) {
             if (!key.equals(element)) result.add(element);
         }
diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
index 21c9c0d..de5f515 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
@@ -45,13 +45,13 @@
     private static String[] sPredefinedKeyboardLayoutSet;
     // Keyboard layout to its display name map.
     private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap =
-            new HashMap<String, String>();
+            CollectionUtils.newHashMap();
     // Keyboard layout to subtype name resource id map.
     private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap =
-            new HashMap<String, Integer>();
+            CollectionUtils.newHashMap();
     // Exceptional locale to subtype name resource id map.
     private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap =
-            new HashMap<String, Integer>();
+            CollectionUtils.newHashMap();
     private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX =
             "string/subtype_generic_";
     private static final String SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX =
@@ -60,11 +60,11 @@
             "string/subtype_no_language_";
     // Exceptional locales to display name map.
     private static final HashMap<String, String> sExceptionalDisplayNamesMap =
-            new HashMap<String, String>();
+            CollectionUtils.newHashMap();
     // Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value.
     // This is for compatibility to keep the same subtype ids as pre-JellyBean.
     private static final HashMap<String,String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
-            new HashMap<String,String>();
+            CollectionUtils.newHashMap();
 
     private SubtypeLocale() {
         // Intentional empty constructor for utility class.
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index a7a5fcb..c693edc 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.inputmethodservice.InputMethodService;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.os.AsyncTask;
@@ -42,7 +43,6 @@
     private static final String TAG = SubtypeSwitcher.class.getSimpleName();
 
     private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
-    private /* final */ LatinIME mService;
     private /* final */ InputMethodManager mImm;
     private /* final */ Resources mResources;
     private /* final */ ConnectivityManager mConnectivityManager;
@@ -68,11 +68,11 @@
             return mEnabledSubtypeCount >= 2 || !mIsSystemLanguageSameAsInputLanguage;
         }
 
-        public void updateEnabledSubtypeCount(int count) {
+        public void updateEnabledSubtypeCount(final int count) {
             mEnabledSubtypeCount = count;
         }
 
-        public void updateIsSystemLanguageSameAsInputLanguage(boolean isSame) {
+        public void updateIsSystemLanguageSameAsInputLanguage(final boolean isSame) {
             mIsSystemLanguageSameAsInputLanguage = isSame;
         }
     }
@@ -81,18 +81,17 @@
         return sInstance;
     }
 
-    public static void init(LatinIME service) {
-        SubtypeLocale.init(service);
-        sInstance.initialize(service);
-        sInstance.updateAllParameters();
+    public static void init(final Context context) {
+        SubtypeLocale.init(context);
+        sInstance.initialize(context);
+        sInstance.updateAllParameters(context);
     }
 
     private SubtypeSwitcher() {
         // Intentional empty constructor for singleton.
     }
 
-    private void initialize(LatinIME service) {
-        mService = service;
+    private void initialize(final Context service) {
         mResources = service.getResources();
         mImm = ImfUtils.getInputMethodManager(service);
         mConnectivityManager = (ConnectivityManager) service.getSystemService(
@@ -111,39 +110,46 @@
 
     // Update all parameters stored in SubtypeSwitcher.
     // Only configuration changed event is allowed to call this because this is heavy.
-    private void updateAllParameters() {
+    private void updateAllParameters(final Context context) {
         mCurrentSystemLocale = mResources.getConfiguration().locale;
-        updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype));
-        updateParametersOnStartInputView();
+        updateSubtype(ImfUtils.getCurrentInputMethodSubtype(context, mNoLanguageSubtype));
+        updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled();
     }
 
-    // Update parameters which are changed outside LatinIME. This parameters affect UI so they
-    // should be updated every time onStartInputview.
-    public void updateParametersOnStartInputView() {
-        updateEnabledSubtypes();
+    /**
+     * Update parameters which are changed outside LatinIME. This parameters affect UI so they
+     * should be updated every time onStartInputView.
+     *
+     * @return true if the current subtype is enabled.
+     */
+    public boolean updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled() {
+        final boolean currentSubtypeEnabled =
+                updateEnabledSubtypesAndReturnIfEnabled(mCurrentSubtype);
         updateShortcutIME();
+        return currentSubtypeEnabled;
     }
 
-    // Reload enabledSubtypes from the framework.
-    private void updateEnabledSubtypes() {
-        final InputMethodSubtype currentSubtype = mCurrentSubtype;
-        boolean foundCurrentSubtypeBecameDisabled = true;
+    /**
+     * Update enabled subtypes from the framework.
+     *
+     * @param subtype the subtype to be checked
+     * @return true if the {@code subtype} is enabled.
+     */
+    private boolean updateEnabledSubtypesAndReturnIfEnabled(final InputMethodSubtype subtype) {
         final List<InputMethodSubtype> enabledSubtypesOfThisIme =
                 mImm.getEnabledInputMethodSubtypeList(null, true);
-        for (InputMethodSubtype ims : enabledSubtypesOfThisIme) {
-            if (ims.equals(currentSubtype)) {
-                foundCurrentSubtypeBecameDisabled = false;
-            }
-        }
         mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size());
-        if (foundCurrentSubtypeBecameDisabled) {
-            if (DBG) {
-                Log.w(TAG, "Last subtype: "
-                        + currentSubtype.getLocale() + "/" + currentSubtype.getExtraValue());
-                Log.w(TAG, "Last subtype was disabled. Update to the current one.");
+
+        for (final InputMethodSubtype ims : enabledSubtypesOfThisIme) {
+            if (ims.equals(subtype)) {
+                return true;
             }
-            updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype));
         }
+        if (DBG) {
+            Log.w(TAG, "Subtype: " + subtype.getLocale() + "/" + subtype.getExtraValue()
+                    + " was disabled");
+        }
+        return false;
     }
 
     private void updateShortcutIME() {
@@ -159,8 +165,8 @@
                 mImm.getShortcutInputMethodsAndSubtypes();
         mShortcutInputMethodInfo = null;
         mShortcutSubtype = null;
-        for (InputMethodInfo imi : shortcuts.keySet()) {
-            List<InputMethodSubtype> subtypes = shortcuts.get(imi);
+        for (final InputMethodInfo imi : shortcuts.keySet()) {
+            final List<InputMethodSubtype> subtypes = shortcuts.get(imi);
             // TODO: Returns the first found IMI for now. Should handle all shortcuts as
             // appropriate.
             mShortcutInputMethodInfo = imi;
@@ -194,24 +200,24 @@
 
         mCurrentSubtype = newSubtype;
         updateShortcutIME();
-        mService.onRefreshKeyboard();
     }
 
     ////////////////////////////
     // Shortcut IME functions //
     ////////////////////////////
 
-    public void switchToShortcutIME() {
+    public void switchToShortcutIME(final InputMethodService context) {
         if (mShortcutInputMethodInfo == null) {
             return;
         }
 
         final String imiId = mShortcutInputMethodInfo.getId();
-        switchToTargetIME(imiId, mShortcutSubtype);
+        switchToTargetIME(imiId, mShortcutSubtype, context);
     }
 
-    private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype) {
-        final IBinder token = mService.getWindow().getWindow().getAttributes().token;
+    private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype,
+            final InputMethodService context) {
+        final IBinder token = context.getWindow().getWindow().getAttributes().token;
         if (token == null) {
             return;
         }
@@ -253,7 +259,7 @@
         return true;
     }
 
-    public void onNetworkStateChanged(Intent intent) {
+    public void onNetworkStateChanged(final Intent intent) {
         final boolean noConnection = intent.getBooleanExtra(
                 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
         mIsNetworkConnected = !noConnection;
@@ -265,7 +271,7 @@
     // Subtype Switching functions //
     //////////////////////////////////
 
-    public boolean needsToDisplayLanguage(Locale keyboardLocale) {
+    public boolean needsToDisplayLanguage(final Locale keyboardLocale) {
         if (keyboardLocale.toString().equals(SubtypeLocale.NO_LANGUAGE)) {
             return true;
         }
@@ -279,12 +285,14 @@
         return SubtypeLocale.getSubtypeLocale(mCurrentSubtype);
     }
 
-    public void onConfigurationChanged(Configuration conf) {
+    public boolean onConfigurationChanged(final Configuration conf, final Context context) {
         final Locale systemLocale = conf.locale;
+        final boolean systemLocaleChanged = !systemLocale.equals(mCurrentSystemLocale);
         // If system configuration was changed, update all parameters.
-        if (!systemLocale.equals(mCurrentSystemLocale)) {
-            updateAllParameters();
+        if (systemLocaleChanged) {
+            updateAllParameters(context);
         }
+        return systemLocaleChanged;
     }
 
     public InputMethodSubtype getCurrentSubtype() {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index c753226..51ed096 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -51,7 +51,7 @@
     private Dictionary mMainDictionary;
     private ContactsBinaryDictionary mContactsDict;
     private final ConcurrentHashMap<String, Dictionary> mDictionaries =
-            new ConcurrentHashMap<String, Dictionary>();
+            CollectionUtils.newConcurrentHashMap();
 
     public static final int MAX_SUGGESTIONS = 18;
 
@@ -242,7 +242,7 @@
         }
 
         final ArrayList<SuggestedWordInfo> suggestionsContainer =
-                new ArrayList<SuggestedWordInfo>(suggestionsSet);
+                CollectionUtils.newArrayList(suggestionsSet);
         final int suggestionsCount = suggestionsContainer.size();
         final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
         final boolean isAllUpperCase = wordComposer.isAllUpperCase();
@@ -307,7 +307,7 @@
         }
 
         final ArrayList<SuggestedWordInfo> suggestionsContainer =
-                new ArrayList<SuggestedWordInfo>(suggestionsSet);
+                CollectionUtils.newArrayList(suggestionsSet);
         final int suggestionsCount = suggestionsContainer.size();
         final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
         final boolean isAllUpperCase = wordComposer.isAllUpperCase();
@@ -338,7 +338,7 @@
         typedWordInfo.setDebugString("+");
         final int suggestionsSize = suggestions.size();
         final ArrayList<SuggestedWordInfo> suggestionsList =
-                new ArrayList<SuggestedWordInfo>(suggestionsSize);
+                CollectionUtils.newArrayList(suggestionsSize);
         suggestionsList.add(typedWordInfo);
         // Note: i here is the index in mScores[], but the index in mSuggestions is one more
         // than i because we added the typed word to mSuggestions without touching mScores.
@@ -391,7 +391,7 @@
     }
 
     public void close() {
-        final HashSet<Dictionary> dictionaries = new HashSet<Dictionary>();
+        final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
         dictionaries.addAll(mDictionaries.values());
         for (final Dictionary dictionary : dictionaries) {
             dictionary.close();
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 88fc006..68ecfa0 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -24,8 +24,10 @@
 import java.util.HashSet;
 
 public class SuggestedWords {
+    private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST =
+            CollectionUtils.newArrayList(0);
     public static final SuggestedWords EMPTY = new SuggestedWords(
-            new ArrayList<SuggestedWordInfo>(0), false, false, false, false, false);
+            EMPTY_WORD_INFO_LIST, false, false, false, false, false);
 
     public final boolean mTypedWordValid;
     // Note: this INCLUDES cases where the word will auto-correct to itself. A good definition
@@ -83,7 +85,7 @@
 
     public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions(
             final CompletionInfo[] infos) {
-        final ArrayList<SuggestedWordInfo> result = new ArrayList<SuggestedWordInfo>();
+        final ArrayList<SuggestedWordInfo> result = CollectionUtils.newArrayList();
         for (CompletionInfo info : infos) {
             if (null != info && info.getText() != null) {
                 result.add(new SuggestedWordInfo(info.getText(), SuggestedWordInfo.MAX_SCORE,
@@ -97,8 +99,8 @@
     // and replace it with what the user currently typed.
     public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
             final CharSequence typedWord, final SuggestedWords previousSuggestions) {
-        final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<SuggestedWordInfo>();
-        final HashSet<String> alreadySeen = new HashSet<String>();
+        final ArrayList<SuggestedWordInfo> suggestionsList = CollectionUtils.newArrayList();
+        final HashSet<String> alreadySeen = CollectionUtils.newHashSet();
         suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
                 SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_USER_TYPED));
         alreadySeen.add(typedWord.toString());
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
index d516e72..6c9d1c2 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
@@ -93,10 +93,10 @@
 
     private final static HashMap<String, String> sDictProjectionMap;
     private final static ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
-            sLangDictCache = new ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>();
+            sLangDictCache = CollectionUtils.newConcurrentHashMap();
 
     static {
-        sDictProjectionMap = new HashMap<String, String>();
+        sDictProjectionMap = CollectionUtils.newHashMap();
         sDictProjectionMap.put(MAIN_COLUMN_ID, MAIN_COLUMN_ID);
         sDictProjectionMap.put(MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD1);
         sDictProjectionMap.put(MAIN_COLUMN_WORD2, MAIN_COLUMN_WORD2);
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
index 610652a..bb0f542 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
@@ -29,9 +29,8 @@
 public class UserHistoryDictionaryBigramList {
     public static final byte FORGETTING_CURVE_INITIAL_VALUE = 0;
     private static final String TAG = UserHistoryDictionaryBigramList.class.getSimpleName();
-    private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = new HashMap<String, Byte>();
-    private final HashMap<String, HashMap<String, Byte>> mBigramMap =
-            new HashMap<String, HashMap<String, Byte>>();
+    private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = CollectionUtils.newHashMap();
+    private final HashMap<String, HashMap<String, Byte>> mBigramMap = CollectionUtils.newHashMap();
     private int mSize = 0;
 
     public void evictAll() {
@@ -57,7 +56,7 @@
         if (mBigramMap.containsKey(word1)) {
             map = mBigramMap.get(word1);
         } else {
-            map = new HashMap<String, Byte>();
+            map = CollectionUtils.newHashMap();
             mBigramMap.put(word1, map);
         }
         if (!map.containsKey(word2)) {
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index c6b5c33..fc7a421 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -65,44 +65,6 @@
         }
     }
 
-    public static class GCUtils {
-        private static final String GC_TAG = GCUtils.class.getSimpleName();
-        public static final int GC_TRY_COUNT = 2;
-        // GC_TRY_LOOP_MAX is used for the hard limit of GC wait,
-        // GC_TRY_LOOP_MAX should be greater than GC_TRY_COUNT.
-        public static final int GC_TRY_LOOP_MAX = 5;
-        private static final long GC_INTERVAL = DateUtils.SECOND_IN_MILLIS;
-        private static GCUtils sInstance = new GCUtils();
-        private int mGCTryCount = 0;
-
-        public static GCUtils getInstance() {
-            return sInstance;
-        }
-
-        public void reset() {
-            mGCTryCount = 0;
-        }
-
-        public boolean tryGCOrWait(String metaData, Throwable t) {
-            if (mGCTryCount == 0) {
-                System.gc();
-            }
-            if (++mGCTryCount > GC_TRY_COUNT) {
-                LatinImeLogger.logOnException(metaData, t);
-                return false;
-            } else {
-                try {
-                    Thread.sleep(GC_INTERVAL);
-                    return true;
-                } catch (InterruptedException e) {
-                    Log.e(GC_TAG, "Sleep was interrupted.");
-                    LatinImeLogger.logOnException(metaData, t);
-                    return false;
-                }
-            }
-        }
-    }
-
     /* package */ static class RingCharBuffer {
         private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
         private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
@@ -477,7 +439,7 @@
 
     private static final String HARDWARE_PREFIX = Build.HARDWARE + ",";
     private static final HashMap<String, String> sDeviceOverrideValueMap =
-            new HashMap<String, String>();
+            CollectionUtils.newHashMap();
 
     public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) {
         final int orientation = res.getConfiguration().orientation;
@@ -495,7 +457,7 @@
         return sDeviceOverrideValueMap.get(key);
     }
 
-    private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = new HashMap<String, Long>();
+    private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = CollectionUtils.newHashMap();
     private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
     public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
         if (TextUtils.isEmpty(str)) {
@@ -506,7 +468,7 @@
         if (N < 2 || N % 2 != 0) {
             return EMPTY_LT_HASH_MAP;
         }
-        final HashMap<String, Long> retval = new HashMap<String, Long>();
+        final HashMap<String, Long> retval = CollectionUtils.newHashMap();
         for (int i = 0; i < N / 2; ++i) {
             final String localeStr = ss[i * 2];
             final long time = Long.valueOf(ss[i * 2 + 1]);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index d05dc02..eef7a51 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -25,6 +25,7 @@
 
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.ContactsBinaryDictionary;
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.DictionaryCollection;
@@ -62,10 +63,9 @@
     public static final int CAPITALIZE_ALL = 2; // All caps
 
     private final static String[] EMPTY_STRING_ARRAY = new String[0];
-    private Map<String, DictionaryPool> mDictionaryPools =
-            Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
+    private Map<String, DictionaryPool> mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
     private Map<String, UserBinaryDictionary> mUserDictionaries =
-            Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
+            CollectionUtils.newSynchronizedTreeMap();
     private ContactsBinaryDictionary mContactsDictionary;
 
     // The threshold for a candidate to be offered as a suggestion.
@@ -77,7 +77,7 @@
     private final Object mUseContactsLock = new Object();
 
     private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList =
-            new HashSet<WeakReference<DictionaryCollection>>();
+            CollectionUtils.newHashSet();
 
     public static final int SCRIPT_LATIN = 0;
     public static final int SCRIPT_CYRILLIC = 1;
@@ -93,7 +93,7 @@
         // proximity to pass to the dictionary descent algorithm.
         // IMPORTANT: this only contains languages - do not write countries in there.
         // Only the language is searched from the map.
-        mLanguageToScript = new TreeMap<String, Integer>();
+        mLanguageToScript = CollectionUtils.newTreeMap();
         mLanguageToScript.put("en", SCRIPT_LATIN);
         mLanguageToScript.put("fr", SCRIPT_LATIN);
         mLanguageToScript.put("de", SCRIPT_LATIN);
@@ -231,7 +231,7 @@
             mSuggestionThreshold = suggestionThreshold;
             mRecommendedThreshold = recommendedThreshold;
             mMaxLength = maxLength;
-            mSuggestions = new ArrayList<CharSequence>(maxLength + 1);
+            mSuggestions = CollectionUtils.newArrayList(maxLength + 1);
             mScores = new int[mMaxLength];
         }
 
@@ -359,10 +359,9 @@
 
     private void closeAllDictionaries() {
         final Map<String, DictionaryPool> oldPools = mDictionaryPools;
-        mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
+        mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
         final Map<String, UserBinaryDictionary> oldUserDictionaries = mUserDictionaries;
-        mUserDictionaries =
-                Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
+        mUserDictionaries = CollectionUtils.newSynchronizedTreeMap();
         new Thread("spellchecker_close_dicts") {
             @Override
             public void run() {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index 501a0e2..5a1bd37 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -22,6 +22,8 @@
 import android.view.textservice.SuggestionsInfo;
 import android.view.textservice.TextInfo;
 
+import com.android.inputmethod.latin.CollectionUtils;
+
 import java.util.ArrayList;
 
 public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
@@ -40,10 +42,10 @@
             return null;
         }
         final int N = ssi.getSuggestionsCount();
-        final ArrayList<Integer> additionalOffsets = new ArrayList<Integer>();
-        final ArrayList<Integer> additionalLengths = new ArrayList<Integer>();
+        final ArrayList<Integer> additionalOffsets = CollectionUtils.newArrayList();
+        final ArrayList<Integer> additionalLengths = CollectionUtils.newArrayList();
         final ArrayList<SuggestionsInfo> additionalSuggestionsInfos =
-                new ArrayList<SuggestionsInfo>();
+                CollectionUtils.newArrayList();
         String currentWord = null;
         for (int i = 0; i < N; ++i) {
             final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index 218eab7..53aa6c7 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -19,6 +19,7 @@
 import android.util.Log;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.WordComposer;
@@ -45,7 +46,7 @@
     private final Locale mLocale;
     private int mSize;
     private volatile boolean mClosed;
-    final static ArrayList<SuggestedWordInfo> noSuggestions = new ArrayList<SuggestedWordInfo>();
+    final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList();
     private final static DictAndProximity dummyDict = new DictAndProximity(
             new Dictionary(Dictionary.TYPE_MAIN) {
                 @Override
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index 1762e29..fe5225e 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.latin.spellcheck;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
 
 import java.util.TreeMap;
@@ -59,7 +60,7 @@
         // character.
         // Since we need to build such an array, we want to be able to search in our big proximity
         // data quickly by character, and a map is probably the best way to do this.
-        final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
+        final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
 
         // The proximity here is the union of
         // - the proximity for a QWERTY keyboard.
@@ -122,7 +123,7 @@
     }
 
     private static class Cyrillic {
-        final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
+        final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
         // TODO: The following table is solely based on the keyboard layout. Consult with Russian
         // speakers on commonly misspelled words/letters.
         final static int[] PROXIMITY = {
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index bd9a0da..afc4293 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -58,6 +58,7 @@
 import com.android.inputmethod.keyboard.PointerTracker;
 import com.android.inputmethod.keyboard.ViewLayoutUtils;
 import com.android.inputmethod.latin.AutoCorrection;
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
@@ -88,9 +89,9 @@
     private final MoreSuggestions.Builder mMoreSuggestionsBuilder;
     private final PopupWindow mMoreSuggestionsWindow;
 
-    private final ArrayList<TextView> mWords = new ArrayList<TextView>();
-    private final ArrayList<TextView> mInfos = new ArrayList<TextView>();
-    private final ArrayList<View> mDividers = new ArrayList<View>();
+    private final ArrayList<TextView> mWords = CollectionUtils.newArrayList();
+    private final ArrayList<TextView> mInfos = CollectionUtils.newArrayList();
+    private final ArrayList<View> mDividers = CollectionUtils.newArrayList();
 
     private final PopupWindow mPreviewPopup;
     private final TextView mPreviewText;
@@ -167,7 +168,7 @@
 
         private final int mSuggestionStripOption;
 
-        private final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
+        private final ArrayList<CharSequence> mTexts = CollectionUtils.newArrayList();
 
         public boolean mMoreSuggestionsAvailable;
 
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 845ba9c..09a22ef 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -34,15 +34,18 @@
 import android.inputmethodservice.InputMethodService;
 import android.os.Build;
 import android.os.IBinder;
+import android.os.SystemClock;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Log;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.widget.Button;
@@ -51,9 +54,9 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.KeyboardView;
 import com.android.inputmethod.keyboard.MainKeyboardView;
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.LatinIME;
@@ -112,7 +115,7 @@
     // the system to do so.
     /* package */ ResearchLog mIntentionalResearchLog;
     // LogUnits are queued here and released only when the user requests the intentional log.
-    private List<LogUnit> mIntentionalResearchLogQueue = new ArrayList<LogUnit>();
+    private List<LogUnit> mIntentionalResearchLogQueue = CollectionUtils.newArrayList();
 
     private boolean mIsPasswordView = false;
     private boolean mIsLoggingSuspended = false;
@@ -134,20 +137,21 @@
     // used to check whether words are not unique
     private Suggest mSuggest;
     private Dictionary mDictionary;
-    private KeyboardSwitcher mKeyboardSwitcher;
+    private MainKeyboardView mMainKeyboardView;
     private InputMethodService mInputMethodService;
+    private final Statistics mStatistics;
 
     private ResearchLogUploader mResearchLogUploader;
 
     private ResearchLogger() {
+        mStatistics = Statistics.getInstance();
     }
 
     public static ResearchLogger getInstance() {
         return sInstance;
     }
 
-    public void init(final InputMethodService ims, final SharedPreferences prefs,
-            KeyboardSwitcher keyboardSwitcher) {
+    public void init(final InputMethodService ims, final SharedPreferences prefs) {
         assert ims != null;
         if (ims == null) {
             Log.w(TAG, "IMS is null; logging is off");
@@ -179,7 +183,6 @@
         }
         mResearchLogUploader = new ResearchLogUploader(ims, mFilesDir);
         mResearchLogUploader.start();
-        mKeyboardSwitcher = keyboardSwitcher;
         mInputMethodService = ims;
         mPrefs = prefs;
     }
@@ -193,10 +196,15 @@
         }
     }
 
-    public void mainKeyboardView_onAttachedToWindow() {
+    public void mainKeyboardView_onAttachedToWindow(final MainKeyboardView mainKeyboardView) {
+        mMainKeyboardView = mainKeyboardView;
         maybeShowSplashScreen();
     }
 
+    public void mainKeyboardView_onDetachedFromWindow() {
+        mMainKeyboardView = null;
+    }
+
     private boolean hasSeenSplash() {
         return mPrefs.getBoolean(PREF_RESEARCH_HAS_SEEN_SPLASH, false);
     }
@@ -210,7 +218,8 @@
         if (mSplashDialog != null && mSplashDialog.isShowing()) {
             return;
         }
-        final IBinder windowToken = mKeyboardSwitcher.getMainKeyboardView().getWindowToken();
+        final IBinder windowToken = mMainKeyboardView != null
+                ? mMainKeyboardView.getWindowToken() : null;
         if (windowToken == null) {
             return;
         }
@@ -269,10 +278,35 @@
         return new File(filesDir, sb.toString());
     }
 
+    private void checkForEmptyEditor() {
+        if (mInputMethodService == null) {
+            return;
+        }
+        final InputConnection ic = mInputMethodService.getCurrentInputConnection();
+        if (ic == null) {
+            return;
+        }
+        final CharSequence textBefore = ic.getTextBeforeCursor(1, 0);
+        if (!TextUtils.isEmpty(textBefore)) {
+            mStatistics.setIsEmptyUponStarting(false);
+            return;
+        }
+        final CharSequence textAfter = ic.getTextAfterCursor(1, 0);
+        if (!TextUtils.isEmpty(textAfter)) {
+            mStatistics.setIsEmptyUponStarting(false);
+            return;
+        }
+        if (textBefore != null && textAfter != null) {
+            mStatistics.setIsEmptyUponStarting(true);
+        }
+    }
+
     private void start() {
         maybeShowSplashScreen();
         updateSuspendedState();
         requestIndicatorRedraw();
+        mStatistics.reset();
+        checkForEmptyEditor();
         if (!isAllowedToLog()) {
             // Log.w(TAG, "not in usability mode; not logging");
             return;
@@ -296,6 +330,10 @@
     }
 
     /* package */ void stop() {
+        logStatistics();
+        publishLogUnit(mCurrentLogUnit, true);
+        mCurrentLogUnit = new LogUnit();
+
         if (mMainResearchLog != null) {
             mMainResearchLog.stop();
         }
@@ -304,6 +342,26 @@
         }
     }
 
+    private static final String[] EVENTKEYS_STATISTICS = {
+        "Statistics", "charCount", "letterCount", "numberCount", "spaceCount", "deleteOpsCount",
+        "wordCount", "isEmptyUponStarting", "isEmptinessStateKnown", "averageTimeBetweenKeys",
+        "averageTimeBeforeDelete", "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete"
+    };
+    private static void logStatistics() {
+        final ResearchLogger researchLogger = getInstance();
+        final Statistics statistics = researchLogger.mStatistics;
+        final Object[] values = {
+            statistics.mCharCount, statistics.mLetterCount, statistics.mNumberCount,
+            statistics.mSpaceCount, statistics.mDeleteKeyCount,
+            statistics.mWordCount, statistics.mIsEmptyUponStarting,
+            statistics.mIsEmptinessStateKnown, statistics.mKeyCounter.getAverageTime(),
+            statistics.mBeforeDeleteKeyCounter.getAverageTime(),
+            statistics.mDuringRepeatedDeleteKeysCounter.getAverageTime(),
+            statistics.mAfterDeleteKeyCounter.getAverageTime()
+        };
+        researchLogger.enqueueEvent(EVENTKEYS_STATISTICS, values);
+    }
+
     private void setLoggingAllowed(boolean enableLogging) {
         if (mPrefs == null) {
             return;
@@ -460,7 +518,7 @@
     private void saveLogsForFeedback() {
         mFeedbackLog = mIntentionalResearchLog;
         if (mIntentionalResearchLogQueue != null) {
-            mFeedbackQueue = new ArrayList<LogUnit>(mIntentionalResearchLogQueue);
+            mFeedbackQueue = CollectionUtils.newArrayList(mIntentionalResearchLogQueue);
         } else {
             mFeedbackQueue = null;
         }
@@ -470,7 +528,7 @@
 
         mMainResearchLog = null;
         mIntentionalResearchLog = null;
-        mIntentionalResearchLogQueue = new ArrayList<LogUnit>();
+        mIntentionalResearchLogQueue = CollectionUtils.newArrayList();
     }
 
     private static final int LOG_DRAIN_TIMEOUT_IN_MS = 1000 * 5;
@@ -538,14 +596,10 @@
         if (!IS_SHOWING_INDICATOR) {
             return;
         }
-        if (mKeyboardSwitcher == null) {
+        if (mMainKeyboardView == null) {
             return;
         }
-        final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
-        if (mainKeyboardView == null) {
-            return;
-        }
-        mainKeyboardView.invalidateAllKeys();
+        mMainKeyboardView.invalidateAllKeys();
     }
 
 
@@ -704,6 +758,7 @@
             mLoggingFrequencyState.onWordLogged();
         }
         mCurrentLogUnit = new LogUnit();
+        mStatistics.recordWordEntered();
     }
 
     private void publishLogUnit(LogUnit logUnit, boolean isPrivacySensitive) {
@@ -726,9 +781,9 @@
     }
 
     static class LogUnit {
-        private final List<String[]> mKeysList = new ArrayList<String[]>();
-        private final List<Object[]> mValuesList = new ArrayList<Object[]>();
-        private final List<Boolean> mIsPotentiallyPrivate = new ArrayList<Boolean>();
+        private final List<String[]> mKeysList = CollectionUtils.newArrayList();
+        private final List<Object[]> mValuesList = CollectionUtils.newArrayList();
+        private final List<Boolean> mIsPotentiallyPrivate = CollectionUtils.newArrayList();
 
         private void addLogAtom(final String[] keys, final Object[] values,
                 final Boolean isPotentiallyPrivate) {
@@ -847,20 +902,6 @@
         stop();
     }
 
-    private static final String[] EVENTKEYS_LATINIME_COMMITTEXT = {
-        "LatinIMECommitText", "typedWord"
-    };
-
-    public static void latinIME_commitText(final CharSequence typedWord) {
-        final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
-        final Object[] values = {
-            scrubbedWord
-        };
-        final ResearchLogger researchLogger = getInstance();
-        researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_COMMITTEXT, values);
-        researchLogger.onWordComplete(scrubbedWord);
-    }
-
     private static final String[] EVENTKEYS_USER_FEEDBACK = {
         "UserFeedback", "FeedbackContents"
     };
@@ -912,48 +953,9 @@
         final Object[] values = {
             Keyboard.printableCode(scrubDigitFromCodePoint(code)), x, y
         };
-        getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values);
-    }
-
-    private static final String[] EVENTKEYS_CORRECTION = {
-        "LogCorrection", "subgroup", "before", "after", "position"
-    };
-    public static void logCorrection(final String subgroup, final String before, final String after,
-            final int position) {
-        final Object[] values = {
-            subgroup, scrubDigitsFromString(before), scrubDigitsFromString(after), position
-        };
-        getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_CORRECTION, values);
-    }
-
-    private static final String[] EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION = {
-        "LatinIMECommitCurrentAutoCorrection", "typedWord", "autoCorrection"
-    };
-    public static void latinIME_commitCurrentAutoCorrection(final String typedWord,
-            final String autoCorrection) {
-        final Object[] values = {
-            scrubDigitsFromString(typedWord), scrubDigitsFromString(autoCorrection)
-        };
         final ResearchLogger researchLogger = getInstance();
-        researchLogger.enqueuePotentiallyPrivateEvent(
-                EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION, values);
-    }
-
-    private static final String[] EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT = {
-        "LatinIMEDeleteSurroundingText", "length"
-    };
-    public static void latinIME_deleteSurroundingText(final int length) {
-        final Object[] values = {
-            length
-        };
-        getInstance().enqueueEvent(EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT, values);
-    }
-
-    private static final String[] EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD = {
-        "LatinIMEDoubleSpaceAutoPeriod"
-    };
-    public static void latinIME_doubleSpaceAutoPeriod() {
-        getInstance().enqueueEvent(EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD, EVENTKEYS_NULLVALUES);
+        researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values);
+        researchLogger.mStatistics.recordChar(code, SystemClock.uptimeMillis());
     }
 
     private static final String[] EVENTKEYS_LATINIME_ONDISPLAYCOMPLETIONS = {
@@ -980,6 +982,10 @@
     public static void latinIME_onWindowHidden(final int savedSelectionStart,
             final int savedSelectionEnd, final InputConnection ic) {
         if (ic != null) {
+            // Capture the TextView contents.  This will trigger onUpdateSelection(), so we
+            // set sLatinIMEExpectingUpdateSelection so that when onUpdateSelection() is called,
+            // it can tell that it was generated by the logging code, and not by the user, and
+            // therefore keep user-visible state as is.
             ic.beginBatchEdit();
             ic.performContextMenuAction(android.R.id.selectAll);
             CharSequence charSequence = ic.getSelectedText(0);
@@ -1049,29 +1055,6 @@
         researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONUPDATESELECTION, values);
     }
 
-    private static final String[] EVENTKEYS_LATINIME_PERFORMEDITORACTION = {
-        "LatinIMEPerformEditorAction", "imeActionNext"
-    };
-    public static void latinIME_performEditorAction(final int imeActionNext) {
-        final Object[] values = {
-            imeActionNext
-        };
-        getInstance().enqueueEvent(EVENTKEYS_LATINIME_PERFORMEDITORACTION, values);
-    }
-
-    private static final String[] EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION = {
-        "LatinIMEPickApplicationSpecifiedCompletion", "index", "text", "x", "y"
-    };
-    public static void latinIME_pickApplicationSpecifiedCompletion(final int index,
-            final CharSequence cs) {
-        final Object[] values = {
-            index, cs, Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE
-        };
-        final ResearchLogger researchLogger = getInstance();
-        researchLogger.enqueuePotentiallyPrivateEvent(
-                EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION, values);
-    }
-
     private static final String[] EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY = {
         "LatinIMEPickSuggestionManually", "replacedWord", "index", "suggestion", "x", "y"
     };
@@ -1099,21 +1082,6 @@
         getInstance().enqueueEvent(EVENTKEYS_LATINIME_PUNCTUATIONSUGGESTION, values);
     }
 
-    private static final String[] EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT = {
-        "LatinIMERevertDoubleSpaceWhileInBatchEdit"
-    };
-    public static void latinIME_revertDoubleSpaceWhileInBatchEdit() {
-        getInstance().enqueueEvent(EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT,
-                EVENTKEYS_NULLVALUES);
-    }
-
-    private static final String[] EVENTKEYS_LATINIME_REVERTSWAPPUNCTUATION = {
-        "LatinIMERevertSwapPunctuation"
-    };
-    public static void latinIME_revertSwapPunctuation() {
-        getInstance().enqueueEvent(EVENTKEYS_LATINIME_REVERTSWAPPUNCTUATION, EVENTKEYS_NULLVALUES);
-    }
-
     private static final String[] EVENTKEYS_LATINIME_SENDKEYCODEPOINT = {
         "LatinIMESendKeyCodePoint", "code"
     };
@@ -1124,12 +1092,11 @@
         getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values);
     }
 
-    private static final String[] EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT = {
-        "LatinIMESwapSwapperAndSpaceWhileInBatchEdit"
+    private static final String[] EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACE = {
+        "LatinIMESwapSwapperAndSpace"
     };
-    public static void latinIME_swapSwapperAndSpaceWhileInBatchEdit() {
-        getInstance().enqueueEvent(EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT,
-                EVENTKEYS_NULLVALUES);
+    public static void latinIME_swapSwapperAndSpace() {
+        getInstance().enqueueEvent(EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACE, EVENTKEYS_NULLVALUES);
     }
 
     private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_ONLONGPRESS = {
@@ -1248,6 +1215,109 @@
         getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_POINTERTRACKER_ONMOVEEVENT, values);
     }
 
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITCOMPLETION = {
+        "RichInputConnectionCommitCompletion", "completionInfo"
+    };
+    public static void richInputConnection_commitCompletion(final CompletionInfo completionInfo) {
+        final Object[] values = {
+            completionInfo
+        };
+        final ResearchLogger researchLogger = getInstance();
+        researchLogger.enqueuePotentiallyPrivateEvent(
+                EVENTKEYS_RICHINPUTCONNECTION_COMMITCOMPLETION, values);
+    }
+
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITCORRECTION = {
+        "RichInputConnectionCommitCorrection", "CorrectionInfo"
+    };
+    public static void richInputConnection_commitCorrection(CorrectionInfo correctionInfo) {
+        final String typedWord = correctionInfo.getOldText().toString();
+        final String autoCorrection = correctionInfo.getNewText().toString();
+        final Object[] values = {
+            scrubDigitsFromString(typedWord), scrubDigitsFromString(autoCorrection)
+        };
+        final ResearchLogger researchLogger = getInstance();
+        researchLogger.enqueuePotentiallyPrivateEvent(
+                EVENTKEYS_RICHINPUTCONNECTION_COMMITCORRECTION, values);
+    }
+
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITTEXT = {
+        "RichInputConnectionCommitText", "typedWord", "newCursorPosition"
+    };
+    public static void richInputConnection_commitText(final CharSequence typedWord,
+            final int newCursorPosition) {
+        final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
+        final Object[] values = {
+            scrubbedWord, newCursorPosition
+        };
+        final ResearchLogger researchLogger = getInstance();
+        researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_COMMITTEXT,
+                values);
+        researchLogger.onWordComplete(scrubbedWord);
+    }
+
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT = {
+        "RichInputConnectionDeleteSurroundingText", "beforeLength", "afterLength"
+    };
+    public static void richInputConnection_deleteSurroundingText(final int beforeLength,
+            final int afterLength) {
+        final Object[] values = {
+            beforeLength, afterLength
+        };
+        getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT, values);
+    }
+
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT = {
+        "RichInputConnectionFinishComposingText"
+    };
+    public static void richInputConnection_finishComposingText() {
+        getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT,
+                EVENTKEYS_NULLVALUES);
+    }
+
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_PERFORMEDITORACTION = {
+        "RichInputConnectionPerformEditorAction", "imeActionNext"
+    };
+    public static void richInputConnection_performEditorAction(final int imeActionNext) {
+        final Object[] values = {
+            imeActionNext
+        };
+        getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_PERFORMEDITORACTION, values);
+    }
+
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SENDKEYEVENT = {
+        "RichInputConnectionSendKeyEvent", "eventTime", "action", "code"
+    };
+    public static void richInputConnection_sendKeyEvent(final KeyEvent keyEvent) {
+        final Object[] values = {
+            keyEvent.getEventTime(),
+            keyEvent.getAction(),
+            keyEvent.getKeyCode()
+        };
+        getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_SENDKEYEVENT, values);
+    }
+
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SETCOMPOSINGTEXT = {
+        "RichInputConnectionSetComposingText", "text", "newCursorPosition"
+    };
+    public static void richInputConnection_setComposingText(final CharSequence text,
+            final int newCursorPosition) {
+        final Object[] values = {
+            text, newCursorPosition
+        };
+        getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_SETCOMPOSINGTEXT, values);
+    }
+
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SETSELECTION = {
+        "RichInputConnectionSetSelection", "from", "to"
+    };
+    public static void richInputConnection_setSelection(final int from, final int to) {
+        final Object[] values = {
+            from, to
+        };
+        getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_SETSELECTION, values);
+    }
+
     private static final String[] EVENTKEYS_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT = {
         "SuddenJumpingTouchEventHandlerOnTouchEvent", "motionEvent"
     };
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
new file mode 100644
index 0000000..4a2cd07
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.research;
+
+import com.android.inputmethod.keyboard.Keyboard;
+
+public class Statistics {
+    // Number of characters entered during a typing session
+    int mCharCount;
+    // Number of letter characters entered during a typing session
+    int mLetterCount;
+    // Number of number characters entered
+    int mNumberCount;
+    // Number of space characters entered
+    int mSpaceCount;
+    // Number of delete operations entered (taps on the backspace key)
+    int mDeleteKeyCount;
+    // Number of words entered during a session.
+    int mWordCount;
+    // Whether the text field was empty upon editing
+    boolean mIsEmptyUponStarting;
+    boolean mIsEmptinessStateKnown;
+
+    // Timers to count average time to enter a key, first press a delete key,
+    // between delete keys, and then to return typing after a delete key.
+    final AverageTimeCounter mKeyCounter = new AverageTimeCounter();
+    final AverageTimeCounter mBeforeDeleteKeyCounter = new AverageTimeCounter();
+    final AverageTimeCounter mDuringRepeatedDeleteKeysCounter = new AverageTimeCounter();
+    final AverageTimeCounter mAfterDeleteKeyCounter = new AverageTimeCounter();
+
+    static class AverageTimeCounter {
+        int mCount;
+        int mTotalTime;
+
+        public void reset() {
+            mCount = 0;
+            mTotalTime = 0;
+        }
+
+        public void add(long deltaTime) {
+            mCount++;
+            mTotalTime += deltaTime;
+        }
+
+        public int getAverageTime() {
+            if (mCount == 0) {
+                return 0;
+            }
+            return mTotalTime / mCount;
+        }
+    }
+
+    // To account for the interruptions when the user's attention is directed elsewhere, times
+    // longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic.
+    public static final int MIN_TYPING_INTERMISSION = 5 * 1000;  // in milliseconds
+    public static final int MIN_DELETION_INTERMISSION = 15 * 1000;  // in milliseconds
+
+    // The last time that a tap was performed
+    private long mLastTapTime;
+    // The type of the last keypress (delete key or not)
+    boolean mIsLastKeyDeleteKey;
+
+    private static final Statistics sInstance = new Statistics();
+
+    public static Statistics getInstance() {
+        return sInstance;
+    }
+
+    private Statistics() {
+        reset();
+    }
+
+    public void reset() {
+        mCharCount = 0;
+        mLetterCount = 0;
+        mNumberCount = 0;
+        mSpaceCount = 0;
+        mDeleteKeyCount = 0;
+        mWordCount = 0;
+        mIsEmptyUponStarting = true;
+        mIsEmptinessStateKnown = false;
+        mKeyCounter.reset();
+        mBeforeDeleteKeyCounter.reset();
+        mDuringRepeatedDeleteKeysCounter.reset();
+        mAfterDeleteKeyCounter.reset();
+
+        mLastTapTime = 0;
+        mIsLastKeyDeleteKey = false;
+    }
+
+    public void recordChar(int codePoint, long time) {
+        final long delta = time - mLastTapTime;
+        if (codePoint == Keyboard.CODE_DELETE) {
+            mDeleteKeyCount++;
+            if (delta < MIN_DELETION_INTERMISSION) {
+                if (mIsLastKeyDeleteKey) {
+                    mDuringRepeatedDeleteKeysCounter.add(delta);
+                } else {
+                    mBeforeDeleteKeyCounter.add(delta);
+                }
+            }
+            mIsLastKeyDeleteKey = true;
+        } else {
+            mCharCount++;
+            if (Character.isDigit(codePoint)) {
+                mNumberCount++;
+            }
+            if (Character.isLetter(codePoint)) {
+                mLetterCount++;
+            }
+            if (Character.isSpaceChar(codePoint)) {
+                mSpaceCount++;
+            }
+            if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) {
+                mAfterDeleteKeyCounter.add(delta);
+            } else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) {
+                mKeyCounter.add(delta);
+            }
+            mIsLastKeyDeleteKey = false;
+        }
+        mLastTapTime = time;
+    }
+
+    public void recordWordEntered() {
+        mWordCount++;
+    }
+
+    public void setIsEmptyUponStarting(final boolean isEmpty) {
+        mIsEmptyUponStarting = isEmpty;
+        mIsEmptinessStateKnown = true;
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java b/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
index 87501ee..bc50439 100644
--- a/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
@@ -22,6 +22,7 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.latin.AdditionalSubtype;
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.ImfUtils;
 import com.android.inputmethod.latin.StringUtils;
 import com.android.inputmethod.latin.SubtypeLocale;
@@ -31,7 +32,7 @@
 
 public class SpacebarTextTests extends AndroidTestCase {
     // Locale to subtypes list.
-    private final ArrayList<InputMethodSubtype> mSubtypesList = new ArrayList<InputMethodSubtype>();
+    private final ArrayList<InputMethodSubtype> mSubtypesList = CollectionUtils.newArrayList();
 
     private Resources mRes;
 
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
index 3dc4543..1346c00 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
@@ -19,6 +19,8 @@
 import android.app.Instrumentation;
 import android.test.InstrumentationTestCase;
 
+import com.android.inputmethod.latin.CollectionUtils;
+
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -42,7 +44,7 @@
     }
 
     private static String[] getAllResourceIdNames(final Class<?> resourceIdClass) {
-        final ArrayList<String> names = new ArrayList<String>();
+        final ArrayList<String> names = CollectionUtils.newArrayList();
         for (final Field field : resourceIdClass.getFields()) {
             if (field.getType() == Integer.TYPE) {
                 names.add(field.getName());
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index 9feec2b..ffd95f5 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -135,7 +135,6 @@
         mLatinIME.onCreateInputView();
         mLatinIME.onStartInputView(ei, false);
         mInputConnection = ic;
-        mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard();
         changeLanguage("en_US");
     }
 
@@ -253,6 +252,8 @@
             fail("InputMethodSubtype for locale " + locale + " is not enabled");
         }
         SubtypeSwitcher.getInstance().updateSubtype(subtype);
+        mLatinIME.loadKeyboard();
+        mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard();
         waitForDictionaryToBeLoaded();
     }
 
diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
index c70c2fd..52a3745 100644
--- a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
+++ b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
@@ -28,7 +28,7 @@
 
 public class SubtypeLocaleTests extends AndroidTestCase {
     // Locale to subtypes list.
-    private final ArrayList<InputMethodSubtype> mSubtypesList = new ArrayList<InputMethodSubtype>();
+    private final ArrayList<InputMethodSubtype> mSubtypesList = CollectionUtils.newArrayList();
 
     private Resources mRes;
 
diff --git a/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl b/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
index f6c84ea..774094c 100644
--- a/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
+++ b/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.R;
 
 import java.util.HashMap;
@@ -45,14 +46,12 @@
  */
 public final class KeyboardTextsSet {
     // Language to texts map.
-    private static final HashMap<String, String[]> sLocaleToTextsMap =
-            new HashMap<String, String[]>();
-    private static final HashMap<String, Integer> sNameToIdsMap =
-            new HashMap<String, Integer>();
+    private static final HashMap<String, String[]> sLocaleToTextsMap = CollectionUtils.newHashMap();
+    private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
 
     private String[] mTexts;
     // Resource name to text map.
-    private HashMap<String, String> mResourceNameToTextsMap = new HashMap<String, String>();
+    private HashMap<String, String> mResourceNameToTextsMap = CollectionUtils.newHashMap();
 
     public void setLanguage(final String language) {
         mTexts = sLocaleToTextsMap.get(language);