diff --git a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
index 9996a6d6a9a714c86a59fa8894d15e5059160e5a..2e05dd382b53ba7bbca5ded17d0a59c188722eba 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
@@ -22,6 +22,7 @@ import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Rect;
 import android.os.Build;
 import android.support.v4.view.PagerAdapter;
 import android.support.v4.view.ViewPager;
@@ -37,7 +38,9 @@ import android.widget.TabHost;
 import android.widget.TabHost.OnTabChangeListener;
 import android.widget.TextView;
 
+import com.android.inputmethod.keyboard.internal.CodesArrayParser;
 import com.android.inputmethod.keyboard.internal.DynamicGridKeyboard;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
 import com.android.inputmethod.keyboard.internal.ScrollKeyboardView;
 import com.android.inputmethod.keyboard.internal.ScrollViewWithNotifier;
 import com.android.inputmethod.latin.Constants;
@@ -47,7 +50,10 @@ import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.ResourceUtils;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
 import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * View class to implement Emoji keyboards.
@@ -75,16 +81,17 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
 
     private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
 
+    private static final int CATEGORY_UNSPECIFIED = -1;
+    private static final int CATEGORY_RECENTS = 0;
+    private static final int CATEGORY_PEOPLE = 1;
+    private static final int CATEGORY_OBJECTS = 2;
+    private static final int CATEGORY_NATURE = 3;
+    private static final int CATEGORY_PLACES = 4;
+    private static final int CATEGORY_SYMBOLS = 5;
+    private static final int CATEGORY_EMOTICONS = 6;
+
     private static class EmojiCategory {
-        private int mCurrentCategory = CATEGORY_UNSPECIFIED;
-        private static final int CATEGORY_UNSPECIFIED = -1;
-        private static final int CATEGORY_RECENTS = 0;
-        private static final int CATEGORY_PEOPLE = 1;
-        private static final int CATEGORY_OBJECTS = 2;
-        private static final int CATEGORY_NATURE = 3;
-        private static final int CATEGORY_PLACES = 4;
-        private static final int CATEGORY_SYMBOLS = 5;
-        private static final int CATEGORY_EMOTICONS = 6;
+        private static final int DEFAULT_MAX_ROW_SIZE = 3;
         private static final String[] sCategoryName = {
                 "recents",
                 "people",
@@ -111,10 +118,18 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
                 KeyboardId.ELEMENT_EMOJI_CATEGORY4,
                 KeyboardId.ELEMENT_EMOJI_CATEGORY5,
                 KeyboardId.ELEMENT_EMOJI_CATEGORY6, };
+        private Resources mRes;
+        private final KeyboardLayoutSet mLayoutSet;
         private final HashMap<String, Integer> mCategoryNameToIdMap = CollectionUtils.newHashMap();
         private final ArrayList<Integer> mShownCategories = new ArrayList<Integer>();
+        private final ConcurrentHashMap<Long, DynamicGridKeyboard>
+                mCategoryKeyboardMap = new ConcurrentHashMap<Long, DynamicGridKeyboard>();
 
-        public EmojiCategory() {
+        private int mCurrentCategory = CATEGORY_UNSPECIFIED;
+
+        public EmojiCategory(final Resources res, final KeyboardLayoutSet layoutSet) {
+            mRes = res;
+            mLayoutSet = layoutSet;
             for (int i = 0; i < sCategoryName.length; ++i) {
                 mCategoryNameToIdMap.put(sCategoryName[i], i);
             }
@@ -185,12 +200,71 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
             return mShownCategories.get(tabId);
         }
 
-        public int getElementIdFromTabId(int tabId) {
-            return sCategoryElementId[getCategoryFromTabId(tabId)];
+        public DynamicGridKeyboard getKeyboard(int category, int id) {
+            synchronized(mCategoryKeyboardMap) {
+                final long key = (((long) category) << 32) | id;
+                final DynamicGridKeyboard kbd;
+                if (!mCategoryKeyboardMap.containsKey(key)) {
+                    if (category != CATEGORY_RECENTS) {
+                        kbd = new DynamicGridKeyboard(
+                                mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
+                                DEFAULT_MAX_ROW_SIZE);
+                        final Keyboard keyboard =
+                                mLayoutSet.getKeyboard(sCategoryElementId[category]);
+                        // TODO: Calculate maxPageCount dynamically
+                        final Key[][] sortedKeys = sortKeys(keyboard.getKeys(), 21);
+                        for (Key emojiKey : sortedKeys[0]) {
+                            if (emojiKey == null) {
+                                break;
+                            }
+                            kbd.addKeyLast(emojiKey);
+                        }
+                    } else {
+                        kbd = new DynamicGridKeyboard(
+                                mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
+                                DEFAULT_MAX_ROW_SIZE);
+                    }
+                    mCategoryKeyboardMap.put(key, kbd);
+                } else {
+                    kbd = mCategoryKeyboardMap.get(key);
+                }
+                return kbd;
+            }
+        }
+
+        private Key[][] sortKeys(Key[] inKeys, int maxPageCount) {
+            Key[] keys = Arrays.copyOf(inKeys, inKeys.length);
+            Arrays.sort(keys, 0, keys.length, new Comparator<Key>() {
+                @Override
+                public int compare(Key lhs, Key rhs) {
+                    final Rect lHitBox = lhs.getHitBox();
+                    final Rect rHitBox = rhs.getHitBox();
+                    if (lHitBox.top < rHitBox.top) {
+                        return -1;
+                    } else if (lHitBox.top > rHitBox.top) {
+                        return 1;
+                    }
+                    if (lHitBox.left < rHitBox.left) {
+                        return -1;
+                    } else if (lHitBox.left > rHitBox.left) {
+                        return 1;
+                    }
+                    if (lhs.getCode() == rhs.getCode()) {
+                        return 0;
+                    }
+                    return lhs.getCode() < rhs.getCode() ? -1 : 1;
+                }
+            });
+            final int pageCount = (keys.length - 1) / maxPageCount + 1;
+            final Key[][] retval = new Key[pageCount][maxPageCount];
+            for (int i = 0; i < keys.length; ++i) {
+                retval[i / maxPageCount][i % maxPageCount] = keys[i];
+            }
+            return retval;
         }
     }
 
-    private final EmojiCategory mEmojiCategory = new EmojiCategory();
+    private final EmojiCategory mEmojiCategory;
 
     public EmojiKeyboardView(final Context context, final AttributeSet attrs) {
         this(context, attrs, R.attr.emojiKeyboardViewStyle);
@@ -219,6 +293,7 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
                         + res.getDimensionPixelSize(R.dimen.suggestions_strip_height));
         builder.setOptions(false, false, false /* lanuageSwitchKeyEnabled */);
         mLayoutSet = builder.build();
+        mEmojiCategory = new EmojiCategory(context.getResources(), builder.build());
         // TODO: Save/restore recent keys from/to preferences.
     }
 
@@ -388,15 +463,14 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
             mEmojiCategory = emojiCategory;
             mListener = listener;
             mLayoutSet = layoutSet;
-            mRecentsKeyboard = new DynamicGridKeyboard(
-                    layoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS));
+            mRecentsKeyboard = mEmojiCategory.getKeyboard(CATEGORY_RECENTS, 0);
         }
 
         public void addRecentKey(final Key key) {
             if (mEmojiCategory.isInRecentTab()) {
                 return;
             }
-            mRecentsKeyboard.addRecentKey(key);
+            mRecentsKeyboard.addKeyFirst(key);
             final KeyboardView recentKeyboardView =
                     mActiveKeyboardView.get(mEmojiCategory.getRecentTabId());
             if (recentKeyboardView != null) {
@@ -424,9 +498,8 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
 
         @Override
         public Object instantiateItem(final ViewGroup container, final int position) {
-            final int elementId = mEmojiCategory.getElementIdFromTabId(position);
-            final Keyboard keyboard = (elementId == KeyboardId.ELEMENT_EMOJI_RECENTS)
-                    ? mRecentsKeyboard : mLayoutSet.getKeyboard(elementId);
+            final Keyboard keyboard =
+                    mEmojiCategory.getKeyboard(mEmojiCategory.getCategoryFromTabId(position), 0);
             final LayoutInflater inflater = LayoutInflater.from(container.getContext());
             final View view = inflater.inflate(
                     R.layout.emoji_keyboard_page, container, false /* attachToRoot */);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
index a226891b446be9fddf02c7d9894c47d179127926..22708975aa10db32e223fd31a1be36975116b291 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
@@ -23,7 +23,6 @@ import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.ArrayDeque;
-import java.util.Random;
 
 /**
  * This is a Keyboard class where you can add keys dynamically shown in a grid layout
@@ -37,12 +36,12 @@ public class DynamicGridKeyboard extends Keyboard {
     private final int mHorizontalStep;
     private final int mVerticalStep;
     private final int mColumnsNum;
-    private final int mMaxRecentKeyCount;
-    private final ArrayDeque<RecentKey> mRecentKeys = CollectionUtils.newArrayDeque();
+    private final int mMaxKeyCount;
+    private final ArrayDeque<GridKey> mGridKeys = CollectionUtils.newArrayDeque();
 
-    private Key[] mCachedRecentKeys;
+    private Key[] mCachedGridKeys;
 
-    public DynamicGridKeyboard(final Keyboard templateKeyboard) {
+    public DynamicGridKeyboard(final Keyboard templateKeyboard, final int maxRows) {
         super(templateKeyboard);
         final Key key0 = getTemplateKey(TEMPLATE_KEY_CODE_0);
         final Key key1 = getTemplateKey(TEMPLATE_KEY_CODE_1);
@@ -50,8 +49,7 @@ public class DynamicGridKeyboard extends Keyboard {
         mHorizontalStep = Math.abs(key1.getX() - key0.getX());
         mVerticalStep = key0.getHeight() + mVerticalGap;
         mColumnsNum = mBaseWidth / mHorizontalStep;
-        final int rowsNum = mBaseHeight / mVerticalStep;
-        mMaxRecentKeyCount = mColumnsNum * rowsNum;
+        mMaxKeyCount = mColumnsNum * maxRows;
     }
 
     private Key getTemplateKey(final int code) {
@@ -63,27 +61,34 @@ public class DynamicGridKeyboard extends Keyboard {
         throw new RuntimeException("Can't find template key: code=" + code);
     }
 
-    private final Random random = new Random();
+    public void addKeyFirst(final Key usedKey) {
+        addKey(usedKey, true);
+    }
+
+    public void addKeyLast(final Key usedKey) {
+        addKey(usedKey, false);
+    }
 
-    public void addRecentKey(final Key usedKey) {
-        synchronized (mRecentKeys) {
-            mCachedRecentKeys = null;
-            final RecentKey key = (usedKey instanceof RecentKey)
-                    ? (RecentKey)usedKey : new RecentKey(usedKey);
-            while (mRecentKeys.remove(key)) {
+    private void addKey(final Key usedKey, final boolean addFirst) {
+        synchronized (mGridKeys) {
+            mCachedGridKeys = null;
+            final GridKey key = new GridKey(usedKey);
+            while (mGridKeys.remove(key)) {
                 // Remove duplicate keys.
             }
-            mRecentKeys.addFirst(key);
-            while (mRecentKeys.size() > mMaxRecentKeyCount) {
-                mRecentKeys.removeLast();
+            if (addFirst) {
+                mGridKeys.addFirst(key);
+            } else {
+                mGridKeys.addLast(key);
+            }
+            while (mGridKeys.size() > mMaxKeyCount) {
+                mGridKeys.removeLast();
             }
             int index = 0;
-            for (final RecentKey recentKey : mRecentKeys) {
+            for (final GridKey gridKey : mGridKeys) {
                 final int keyX = getKeyX(index);
                 final int keyY = getKeyY(index);
-                final int x = keyX+random.nextInt(recentKey.getWidth());
-                final int y = keyY+random.nextInt(recentKey.getHeight());
-                recentKey.updateCorrdinates(keyX, keyY);
+                gridKey.updateCorrdinates(keyX, keyY);
                 index++;
             }
         }
@@ -101,26 +106,26 @@ public class DynamicGridKeyboard extends Keyboard {
 
     @Override
     public Key[] getKeys() {
-        synchronized (mRecentKeys) {
-            if (mCachedRecentKeys != null) {
-                return mCachedRecentKeys;
+        synchronized (mGridKeys) {
+            if (mCachedGridKeys != null) {
+                return mCachedGridKeys;
             }
-            mCachedRecentKeys = mRecentKeys.toArray(new Key[mRecentKeys.size()]);
-            return mCachedRecentKeys;
+            mCachedGridKeys = mGridKeys.toArray(new Key[mGridKeys.size()]);
+            return mCachedGridKeys;
         }
     }
 
     @Override
     public Key[] getNearestKeys(final int x, final int y) {
-        // TODO: Calculate the nearest key index in mRecentKeys from x and y.
+        // TODO: Calculate the nearest key index in mGridKeys from x and y.
         return getKeys();
     }
 
-    static final class RecentKey extends Key {
+    static final class GridKey extends Key {
         private int mCurrentX;
         private int mCurrentY;
 
-        public RecentKey(final Key originalKey) {
+        public GridKey(final Key originalKey) {
             super(originalKey);
         }
 
@@ -151,7 +156,7 @@ public class DynamicGridKeyboard extends Keyboard {
 
         @Override
         public String toString() {
-            return "RecentKey: " + super.toString();
+            return "GridKey: " + super.toString();
         }
     }
 }