From f3f00006cbe2046abbad3a901b436d67497a40f9 Mon Sep 17 00:00:00 2001
From: Satoshi Kataoka <satok@google.com>
Date: Mon, 16 Sep 2013 17:13:41 +0900
Subject: [PATCH] Save & restore emoji keyboard state

Bug: 10538430
Change-Id: I7982f53f5dfa44071e74da63826acffcdc2547e6
---
 .../keyboard/EmojiKeyboardView.java           | 195 ++++++++++--------
 .../internal/DynamicGridKeyboard.java         |  43 +++-
 .../android/inputmethod/latin/Constants.java  |   4 +
 .../inputmethod/latin/settings/Settings.java  |  24 +++
 4 files changed, 173 insertions(+), 93 deletions(-)

diff --git a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
index 002674eae8..d28b5088c7 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
@@ -19,11 +19,13 @@ package com.android.inputmethod.keyboard;
 import static com.android.inputmethod.latin.Constants.NOT_A_COORDINATE;
 
 import android.content.Context;
+import android.content.SharedPreferences;
 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.preference.PreferenceManager;
 import android.support.v4.view.PagerAdapter;
 import android.support.v4.view.ViewPager;
 import android.util.AttributeSet;
@@ -45,6 +47,7 @@ import com.android.inputmethod.keyboard.internal.ScrollViewWithNotifier;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.ResourceUtils;
 
@@ -80,27 +83,25 @@ 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 final int CATEGORY_ID_UNSPECIFIED = -1;
+    public static final int CATEGORY_ID_RECENTS = 0;
+    public static final int CATEGORY_ID_PEOPLE = 1;
+    public static final int CATEGORY_ID_OBJECTS = 2;
+    public static final int CATEGORY_ID_NATURE = 3;
+    public static final int CATEGORY_ID_PLACES = 4;
+    public static final int CATEGORY_ID_SYMBOLS = 5;
+    public static final int CATEGORY_ID_EMOTICONS = 6;
 
     private static class CategoryProperties {
-        public int mCategory;
+        public int mCategoryId;
         public int mPageCount;
-        public CategoryProperties(final int category, final int pageCount) {
-            mCategory = category;
+        public CategoryProperties(final int categoryId, final int pageCount) {
+            mCategoryId = categoryId;
             mPageCount = pageCount;
         }
     }
 
     private static class EmojiCategory {
-        private static final int DEFAULT_PAGE_KEY_COUNT = 21;
-        private static final int DEFAULT_MAX_ROW_SIZE = 3;
         private static final String[] sCategoryName = {
                 "recents",
                 "people",
@@ -126,8 +127,8 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
                 KeyboardId.ELEMENT_EMOJI_CATEGORY3,
                 KeyboardId.ELEMENT_EMOJI_CATEGORY4,
                 KeyboardId.ELEMENT_EMOJI_CATEGORY5,
-                KeyboardId.ELEMENT_EMOJI_CATEGORY6, };
-        private final Resources mRes;
+                KeyboardId.ELEMENT_EMOJI_CATEGORY6 };
+        private final SharedPreferences mPrefs;
         private final int mMaxPageKeyCount;
         private final KeyboardLayoutSet mLayoutSet;
         private final HashMap<String, Integer> mCategoryNameToIdMap = CollectionUtils.newHashMap();
@@ -136,41 +137,43 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
         private final ConcurrentHashMap<Long, DynamicGridKeyboard>
                 mCategoryKeyboardMap = new ConcurrentHashMap<Long, DynamicGridKeyboard>();
 
-        private int mCurrentCategory = CATEGORY_UNSPECIFIED;
+        private int mCurrentCategoryId = CATEGORY_ID_UNSPECIFIED;
+        private int mCurrentCategoryPageId = 0;
 
-        public EmojiCategory(final Resources res, final KeyboardLayoutSet layoutSet) {
-            mRes = res;
+        public EmojiCategory(final SharedPreferences prefs, final Resources res,
+                final KeyboardLayoutSet layoutSet) {
+            mPrefs = prefs;
             mMaxPageKeyCount = res.getInteger(R.integer.emoji_keyboard_max_key_count);
             mLayoutSet = layoutSet;
             for (int i = 0; i < sCategoryName.length; ++i) {
                 mCategoryNameToIdMap.put(sCategoryName[i], i);
             }
-            addShownCategory(CATEGORY_RECENTS);
+            addShownCategoryId(CATEGORY_ID_RECENTS);
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
-                addShownCategory(CATEGORY_PEOPLE);
-                addShownCategory(CATEGORY_OBJECTS);
-                addShownCategory(CATEGORY_NATURE);
-                addShownCategory(CATEGORY_PLACES);
-                // TODO: Restore last saved category
-                mCurrentCategory = CATEGORY_PEOPLE;
+                addShownCategoryId(CATEGORY_ID_PEOPLE);
+                addShownCategoryId(CATEGORY_ID_OBJECTS);
+                addShownCategoryId(CATEGORY_ID_NATURE);
+                addShownCategoryId(CATEGORY_ID_PLACES);
+                mCurrentCategoryId = CATEGORY_ID_PEOPLE;
             } else {
-                // TODO: Restore last saved category
-                mCurrentCategory = CATEGORY_SYMBOLS;
+                mCurrentCategoryId = CATEGORY_ID_SYMBOLS;
             }
-            addShownCategory(CATEGORY_SYMBOLS);
-            addShownCategory(CATEGORY_EMOTICONS);
+            addShownCategoryId(CATEGORY_ID_SYMBOLS);
+            addShownCategoryId(CATEGORY_ID_EMOTICONS);
+            getKeyboard(CATEGORY_ID_RECENTS, 0 /* cagetoryPageId */)
+                    .loadRecentKeys(mCategoryKeyboardMap.values());
         }
 
-        private void addShownCategory(int category) {
-            // Load a keyboard of category
-            getKeyboard(category, 0);
+        private void addShownCategoryId(int categoryId) {
+            // Load a keyboard of categoryId
+            getKeyboard(categoryId, 0 /* cagetoryPageId */);
             final CategoryProperties properties =
-                    new CategoryProperties(category, getCategoryPageCount(category));
+                    new CategoryProperties(categoryId, getCategoryPageCount(categoryId));
             mShownCategories.add(properties);
         }
 
-        public String getCategoryName(int category, int categoryPageId) {
-            return sCategoryName[category] + "-" + categoryPageId;
+        public String getCategoryName(int categoryId, int categoryPageId) {
+            return sCategoryName[categoryId] + "-" + categoryPageId;
         }
 
         public int getCategoryId(String name) {
@@ -178,61 +181,71 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
             return mCategoryNameToIdMap.get(strings[0]);
         }
 
-        public int getCategoryIcon(int category) {
-            return sCategoryIcon[category];
+        public int getCategoryIcon(int categoryId) {
+            return sCategoryIcon[categoryId];
         }
 
-        public String getCategoryLabel(int category) {
-            return sCategoryLabel[category];
+        public String getCategoryLabel(int categoryId) {
+            return sCategoryLabel[categoryId];
         }
 
         public ArrayList<CategoryProperties> getShownCategories() {
             return mShownCategories;
         }
 
-        public int getCurrentCategory() {
-            // TODO: Record current category.
-            return mCurrentCategory;
+        public int getCurrentCategoryId() {
+            return mCurrentCategoryId;
         }
 
-        public void setCurrentCategory(int category) {
-            mCurrentCategory = category;
+        public void setCurrentCategoryId(int categoryId) {
+            mCurrentCategoryId = categoryId;
+        }
+
+        public void setCurrentCategoryPageId(int id) {
+            mCurrentCategoryPageId = id;
+        }
+
+        public void saveLastTypedCategoryPage() {
+            Settings.writeEmojiCategoryLastTypedId(
+                    mPrefs, mCurrentCategoryId, mCurrentCategoryPageId);
         }
 
         public boolean isInRecentTab() {
-            return mCurrentCategory == CATEGORY_RECENTS;
+            return mCurrentCategoryId == CATEGORY_ID_RECENTS;
         }
 
-        public int getTabIdFromCategory(int category) {
+        public int getTabIdFromCategoryId(int categoryId) {
             for (int i = 0; i < mShownCategories.size(); ++i) {
-                if (mShownCategories.get(i).mCategory == category) {
+                if (mShownCategories.get(i).mCategoryId == categoryId) {
                     return i;
                 }
             }
-            Log.w(TAG, "category not found: " + category);
+            Log.w(TAG, "categoryId not found: " + categoryId);
             return 0;
         }
 
-        // Returns the view pager's page position for the category
-        public int getPageIdFromCategory(int category) {
+        // Returns the view pager's page position for the categoryId
+        public int getPageIdFromCategoryId(int categoryId) {
+            final int lastSavedCategoryPageId =
+                    Settings.readEmojiCategoryLastTypedId(mPrefs, categoryId);
             int sum = 0;
             for (int i = 0; i < mShownCategories.size(); ++i) {
                 final CategoryProperties props = mShownCategories.get(i);
-                if (props.mCategory == category) {
-                    return sum;
+                if (props.mCategoryId == categoryId) {
+                    return sum + lastSavedCategoryPageId;
                 }
                 sum += props.mPageCount;
             }
-            Log.w(TAG, "category not found: " + category);
+            Log.w(TAG, "categoryId not found: " + categoryId);
             return 0;
         }
 
         public int getRecentTabId() {
-            return getTabIdFromCategory(CATEGORY_RECENTS);
+            return getTabIdFromCategoryId(CATEGORY_ID_RECENTS);
         }
 
-        private int getCategoryPageCount(int category) {
-            final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[category]);
+        private int getCategoryPageCount(int categoryId) {
+            final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
             return (keyboard.getKeys().length - 1) / mMaxPageKeyCount + 1;
         }
 
@@ -246,7 +259,7 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
                 final int temp = sum;
                 sum += properties.mPageCount;
                 if (sum > position) {
-                    return new Pair<Integer, Integer>(properties.mCategory, position - temp);
+                    return new Pair<Integer, Integer>(properties.mCategoryId, position - temp);
                 }
             }
             return null;
@@ -262,33 +275,33 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
             return null;
         }
 
-        public DynamicGridKeyboard getKeyboard(int category, int id) {
+        public DynamicGridKeyboard getKeyboard(int categoryId, int id) {
             synchronized(mCategoryKeyboardMap) {
-                final long key = (((long) category) << 32) | id;
+                final long key = (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id;
                 final DynamicGridKeyboard kbd;
                 if (!mCategoryKeyboardMap.containsKey(key)) {
-                    if (category != CATEGORY_RECENTS) {
+                    if (categoryId != CATEGORY_ID_RECENTS) {
                         final Keyboard keyboard =
-                                mLayoutSet.getKeyboard(sCategoryElementId[category]);
-                        // TODO: Calculate maxPageCount dynamically
+                                mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
                         final Key[][] sortedKeys = sortKeys(keyboard.getKeys(), mMaxPageKeyCount);
                         for (int i = 0; i < sortedKeys.length; ++i) {
-                            final DynamicGridKeyboard tempKbd = new DynamicGridKeyboard(
+                            final DynamicGridKeyboard tempKbd = new DynamicGridKeyboard(mPrefs,
                                     mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
-                                    mMaxPageKeyCount);
+                                    mMaxPageKeyCount, categoryId, i /* categoryPageId */);
                             for (Key emojiKey : sortedKeys[i]) {
                                 if (emojiKey == null) {
                                     break;
                                 }
                                 tempKbd.addKeyLast(emojiKey);
                             }
-                            mCategoryKeyboardMap.put((((long) category) << 32) | i, tempKbd);
+                            mCategoryKeyboardMap.put((((long) categoryId)
+                                    << Constants.MAX_INT_BIT_COUNT) | i, tempKbd);
                         }
                         kbd = mCategoryKeyboardMap.get(key);
                     } else {
-                        kbd = new DynamicGridKeyboard(
+                        kbd = new DynamicGridKeyboard(mPrefs,
                                 mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
-                                mMaxPageKeyCount);
+                                mMaxPageKeyCount, categoryId, 0 /* categoryPageId */);
                         mCategoryKeyboardMap.put(key, kbd);
                     }
                 } else {
@@ -367,8 +380,8 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
                 emojiLp.mEmojiKeyboardHeight);
         builder.setOptions(false, false, false /* lanuageSwitchKeyEnabled */);
         mLayoutSet = builder.build();
-        mEmojiCategory = new EmojiCategory(context.getResources(), builder.build());
-        // TODO: Save/restore recent keys from/to preferences.
+        mEmojiCategory = new EmojiCategory(PreferenceManager.getDefaultSharedPreferences(context),
+                context.getResources(), builder.build());
     }
 
     @Override
@@ -384,20 +397,20 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
         setMeasuredDimension(width, height);
     }
 
-    private void addTab(final TabHost host, final int category) {
-        final String tabId = mEmojiCategory.getCategoryName(category, 0 /* categoryPageId */);
+    private void addTab(final TabHost host, final int categoryId) {
+        final String tabId = mEmojiCategory.getCategoryName(categoryId, 0 /* categoryPageId */);
         final TabHost.TabSpec tspec = host.newTabSpec(tabId);
         tspec.setContent(R.id.emoji_keyboard_dummy);
-        if (mEmojiCategory.getCategoryIcon(category) != 0) {
+        if (mEmojiCategory.getCategoryIcon(categoryId) != 0) {
             final ImageView iconView = (ImageView)LayoutInflater.from(getContext()).inflate(
                     R.layout.emoji_keyboard_tab_icon, null);
-            iconView.setImageResource(mEmojiCategory.getCategoryIcon(category));
+            iconView.setImageResource(mEmojiCategory.getCategoryIcon(categoryId));
             tspec.setIndicator(iconView);
         }
-        if (mEmojiCategory.getCategoryLabel(category) != null) {
+        if (mEmojiCategory.getCategoryLabel(categoryId) != null) {
             final TextView textView = (TextView)LayoutInflater.from(getContext()).inflate(
                     R.layout.emoji_keyboard_tab_label, null);
-            textView.setText(mEmojiCategory.getCategoryLabel(category));
+            textView.setText(mEmojiCategory.getCategoryLabel(categoryId));
             textView.setTextColor(mTabLabelColor);
             tspec.setIndicator(textView);
         }
@@ -409,7 +422,7 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
         mTabHost = (TabHost)findViewById(R.id.emoji_category_tabhost);
         mTabHost.setup();
         for (final CategoryProperties properties : mEmojiCategory.getShownCategories()) {
-            addTab(mTabHost, properties.mCategory);
+            addTab(mTabHost, properties.mCategoryId);
         }
         mTabHost.setOnTabChangedListener(this);
         mTabHost.getTabWidget().setStripEnabled(true);
@@ -424,7 +437,7 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
         final EmojiLayoutParams emojiLp = new EmojiLayoutParams(res);
         emojiLp.setPagerProps(mEmojiPager);
 
-        setCurrentCategory(mEmojiCategory.getCurrentCategory(), true /* force */);
+        setCurrentCategoryId(mEmojiCategory.getCurrentCategoryId(), true /* force */);
 
         final LinearLayout actionBar = (LinearLayout)findViewById(R.id.emoji_action_bar);
         emojiLp.setActionBarProps(actionBar);
@@ -451,15 +464,17 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
 
     @Override
     public void onTabChanged(final String tabId) {
-        final int category = mEmojiCategory.getCategoryId(tabId);
-        setCurrentCategory(category, false /* force */);
+        final int categoryId = mEmojiCategory.getCategoryId(tabId);
+        setCurrentCategoryId(categoryId, false /* force */);
     }
 
 
     @Override
     public void onPageSelected(final int position) {
-        setCurrentCategory(mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position).first,
-                false /* force */);
+        final Pair<Integer, Integer> newPos =
+                mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position);
+        setCurrentCategoryId(newPos.first /* categoryId */, false /* force */);
+        mEmojiCategory.setCurrentCategoryPageId(newPos.second /* categoryPageId */);
     }
 
     @Override
@@ -491,6 +506,7 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
     @Override
     public void onKeyClick(final Key key) {
         mEmojiKeyboardAdapter.addRecentKey(key);
+        mEmojiCategory.saveLastTypedCategoryPage();
         final int code = key.getCode();
         if (code == Constants.CODE_OUTPUT_TEXT) {
             mKeyboardActionListener.onTextInput(key.getOutputText());
@@ -507,27 +523,25 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
         mKeyboardActionListener = listener;
     }
 
-    private void setCurrentCategory(final int category, final boolean force) {
-        if (mEmojiCategory.getCurrentCategory() == category && !force) {
+    private void setCurrentCategoryId(final int categoryId, final boolean force) {
+        if (mEmojiCategory.getCurrentCategoryId() == categoryId && !force) {
             return;
         }
 
-        mEmojiCategory.setCurrentCategory(category);
-        final int newTabId = mEmojiCategory.getTabIdFromCategory(category);
-        final int newCategoryPageId = mEmojiCategory.getPageIdFromCategory(category);
+        mEmojiCategory.setCurrentCategoryId(categoryId);
+        final int newTabId = mEmojiCategory.getTabIdFromCategoryId(categoryId);
+        final int newCategoryPageId = mEmojiCategory.getPageIdFromCategoryId(categoryId);
         if (force || mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(
-                mEmojiPager.getCurrentItem()).first != category) {
+                mEmojiPager.getCurrentItem()).first != categoryId) {
             mEmojiPager.setCurrentItem(newCategoryPageId, true /* smoothScroll */);
         }
         if (force || mTabHost.getCurrentTab() != newTabId) {
             mTabHost.setCurrentTab(newTabId);
         }
-        // TODO: Record current category
     }
 
     private static class EmojiKeyboardAdapter extends PagerAdapter {
         private final ScrollKeyboardView.OnKeyClickListener mListener;
-        private final KeyboardLayoutSet mLayoutSet;
         private final DynamicGridKeyboard mRecentsKeyboard;
         private final SparseArray<ScrollKeyboardView> mActiveKeyboardView =
                 CollectionUtils.newSparseArray();
@@ -539,8 +553,7 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
                 final ScrollKeyboardView.OnKeyClickListener listener) {
             mEmojiCategory = emojiCategory;
             mListener = listener;
-            mLayoutSet = layoutSet;
-            mRecentsKeyboard = mEmojiCategory.getKeyboard(CATEGORY_RECENTS, 0);
+            mRecentsKeyboard = mEmojiCategory.getKeyboard(CATEGORY_ID_RECENTS, 0);
         }
 
         public void addRecentKey(final Key key) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
index 1e5ee59f05..f203eb7d79 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
@@ -16,32 +16,41 @@
 
 package com.android.inputmethod.keyboard.internal;
 
+import android.content.SharedPreferences;
 import android.text.TextUtils;
 
+import com.android.inputmethod.keyboard.EmojiKeyboardView;
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.ArrayDeque;
+import java.util.Collection;
 
 /**
  * This is a Keyboard class where you can add keys dynamically shown in a grid layout
  */
-// TODO: Save/restore recent keys from/to preferences.
 public class DynamicGridKeyboard extends Keyboard {
     private static final int TEMPLATE_KEY_CODE_0 = 0x30;
     private static final int TEMPLATE_KEY_CODE_1 = 0x31;
+    // Recent codes are saved as an integer array, so we use comma as a separater.
+    private static final String RECENT_KEY_SEPARATOR = Constants.STRING_COMMA;
 
+    private final SharedPreferences mPrefs;
     private final int mLeftPadding;
     private final int mHorizontalStep;
     private final int mVerticalStep;
     private final int mColumnsNum;
     private final int mMaxKeyCount;
+    private final boolean mIsRecents;
     private final ArrayDeque<GridKey> mGridKeys = CollectionUtils.newArrayDeque();
 
     private Key[] mCachedGridKeys;
 
-    public DynamicGridKeyboard(final Keyboard templateKeyboard, final int maxKeyCount) {
+    public DynamicGridKeyboard(final SharedPreferences prefs, final Keyboard templateKeyboard,
+            final int maxKeyCount, final int categoryId, final int categoryPageId) {
         super(templateKeyboard);
         final Key key0 = getTemplateKey(TEMPLATE_KEY_CODE_0);
         final Key key1 = getTemplateKey(TEMPLATE_KEY_CODE_1);
@@ -50,6 +59,8 @@ public class DynamicGridKeyboard extends Keyboard {
         mVerticalStep = key0.getHeight() + mVerticalGap;
         mColumnsNum = mBaseWidth / mHorizontalStep;
         mMaxKeyCount = maxKeyCount;
+        mIsRecents = categoryId == EmojiKeyboardView.CATEGORY_ID_RECENTS;
+        mPrefs = prefs;
     }
 
     private Key getTemplateKey(final int code) {
@@ -63,6 +74,9 @@ public class DynamicGridKeyboard extends Keyboard {
 
     public void addKeyFirst(final Key usedKey) {
         addKey(usedKey, true);
+        if (mIsRecents) {
+            saveRecentKeys();
+        }
     }
 
     public void addKeyLast(final Key usedKey) {
@@ -94,6 +108,31 @@ public class DynamicGridKeyboard extends Keyboard {
         }
     }
 
+    private void saveRecentKeys() {
+        final StringBuilder sb = new StringBuilder();
+        for (final Key key : mGridKeys) {
+            sb.append(key.getCode()).append(RECENT_KEY_SEPARATOR);
+        }
+        Settings.writeEmojiRecentKeys(mPrefs, sb.toString());
+    }
+
+    public void loadRecentKeys(Collection<DynamicGridKeyboard> keyboards) {
+        final String str = Settings.readEmojiRecentKeys(mPrefs);
+        for (String s : str.split(RECENT_KEY_SEPARATOR)) {
+            if (TextUtils.isEmpty(s)) {
+                continue;
+            }
+            final int code = Integer.valueOf(s);
+            for (DynamicGridKeyboard kbd : keyboards) {
+                final Key key = kbd.getKey(code);
+                if (key != null) {
+                    addKeyLast(key);
+                    break;
+                }
+            }
+        }
+    }
+
     private int getKeyX(final int index) {
         final int column = index % mColumnsNum;
         return column * mHorizontalStep + mLeftPadding;
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 8aec03f71d..029ba02ed0 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -220,7 +220,11 @@ public final class Constants {
         }
     }
 
+    public static final int MAX_INT_BIT_COUNT = 32;
+    public static final String STRING_COMMA = ",";
+
     private Constants() {
         // This utility class is not publicly instantiable.
     }
+
 }
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 8732a59f80..c1a917d441 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -97,6 +97,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
     public static final String PREF_SEND_FEEDBACK = "send_feedback";
     public static final String PREF_ABOUT_KEYBOARD = "about_keyboard";
 
+    // Emoji
+    public static final String PREF_EMOJI_RECENT_KEYS = "emoji_recent_keys";
+    public static final String PREF_EMOJI_CATEGORY_LAST_TYPED_ID = "emoji_category_last_typed_id";
+
     private Resources mRes;
     private SharedPreferences mPrefs;
     private SettingsValues mSettingsValues;
@@ -363,4 +367,24 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
         final String tokenStr = mPrefs.getString(PREF_LAST_USED_PERSONALIZATION_TOKEN, null);
         return StringUtils.hexStringToByteArray(tokenStr);
     }
+
+    public static void writeEmojiRecentKeys(final SharedPreferences prefs, String str) {
+        prefs.edit().putString(PREF_EMOJI_RECENT_KEYS, str).apply();
+    }
+
+    public static String readEmojiRecentKeys(final SharedPreferences prefs) {
+        return prefs.getString(PREF_EMOJI_RECENT_KEYS, "");
+    }
+
+    public static void writeEmojiCategoryLastTypedId(
+            final SharedPreferences prefs, final int category, final int id) {
+        final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + category;
+        prefs.edit().putInt(key, id).apply();
+    }
+
+    public static int readEmojiCategoryLastTypedId(
+            final SharedPreferences prefs, final int category) {
+        final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + category;
+        return prefs.getInt(key, 0);
+    }
 }
-- 
GitLab