| /* |
| * Copyright (C) 2013 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.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.text.format.DateUtils; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.util.Pair; |
| import android.util.SparseArray; |
| import android.view.LayoutInflater; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.ImageView; |
| import android.widget.LinearLayout; |
| import android.widget.TabHost; |
| import android.widget.TabHost.OnTabChangeListener; |
| import android.widget.TextView; |
| |
| import com.android.inputmethod.keyboard.internal.DynamicGridKeyboard; |
| import com.android.inputmethod.keyboard.internal.ScrollKeyboardView; |
| 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; |
| |
| 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. |
| * The Emoji keyboard consists of group of views {@link R.layout#emoji_keyboard_view}. |
| * <ol> |
| * <li> Emoji category tabs. |
| * <li> Delete button. |
| * <li> Emoji keyboard pages that can be scrolled by swiping horizontally or by selecting a tab. |
| * <li> Back to main keyboard button and enter button. |
| * </ol> |
| * Because of the above reasons, this class doesn't extend {@link KeyboardView}. |
| */ |
| public final class EmojiKeyboardView extends LinearLayout implements OnTabChangeListener, |
| ViewPager.OnPageChangeListener, View.OnClickListener, |
| ScrollKeyboardView.OnKeyClickListener { |
| private static final String TAG = EmojiKeyboardView.class.getSimpleName(); |
| private final int mKeyBackgroundId; |
| private final int mEmojiFunctionalKeyBackgroundId; |
| private final KeyboardLayoutSet mLayoutSet; |
| private final ColorStateList mTabLabelColor; |
| private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener; |
| private EmojiKeyboardAdapter mEmojiKeyboardAdapter; |
| |
| private TabHost mTabHost; |
| private ViewPager mEmojiPager; |
| private EmojiCategoryPageIndicatorView mEmojiCategoryPageIndicatorView; |
| |
| private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER; |
| |
| 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 mCategoryId; |
| public int mPageCount; |
| public CategoryProperties(final int categoryId, final int pageCount) { |
| mCategoryId = categoryId; |
| mPageCount = pageCount; |
| } |
| } |
| |
| private static class EmojiCategory { |
| private static final String[] sCategoryName = { |
| "recents", |
| "people", |
| "objects", |
| "nature", |
| "places", |
| "symbols", |
| "emoticons" }; |
| private static final int[] sCategoryIcon = new int[] { |
| R.drawable.ic_emoji_recent_light, |
| R.drawable.ic_emoji_people_light, |
| R.drawable.ic_emoji_objects_light, |
| R.drawable.ic_emoji_nature_light, |
| R.drawable.ic_emoji_places_light, |
| R.drawable.ic_emoji_symbols_light, |
| 0 }; |
| private static final String[] sCategoryLabel = |
| { null, null, null, null, null, null, ":-)" }; |
| private static final int[] sCategoryElementId = { |
| KeyboardId.ELEMENT_EMOJI_RECENTS, |
| KeyboardId.ELEMENT_EMOJI_CATEGORY1, |
| KeyboardId.ELEMENT_EMOJI_CATEGORY2, |
| KeyboardId.ELEMENT_EMOJI_CATEGORY3, |
| KeyboardId.ELEMENT_EMOJI_CATEGORY4, |
| KeyboardId.ELEMENT_EMOJI_CATEGORY5, |
| KeyboardId.ELEMENT_EMOJI_CATEGORY6 }; |
| private final SharedPreferences mPrefs; |
| private final int mMaxPageKeyCount; |
| private final KeyboardLayoutSet mLayoutSet; |
| private final HashMap<String, Integer> mCategoryNameToIdMap = CollectionUtils.newHashMap(); |
| private final ArrayList<CategoryProperties> mShownCategories = |
| CollectionUtils.newArrayList(); |
| private final ConcurrentHashMap<Long, DynamicGridKeyboard> |
| mCategoryKeyboardMap = new ConcurrentHashMap<Long, DynamicGridKeyboard>(); |
| |
| private int mCurrentCategoryId = CATEGORY_ID_UNSPECIFIED; |
| private int mCurrentCategoryPageId = 0; |
| |
| 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); |
| } |
| addShownCategoryId(CATEGORY_ID_RECENTS); |
| if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2 |
| || android.os.Build.VERSION.CODENAME.equalsIgnoreCase("KeyLimePie") |
| || android.os.Build.VERSION.CODENAME.equalsIgnoreCase("KitKat")) { |
| addShownCategoryId(CATEGORY_ID_PEOPLE); |
| addShownCategoryId(CATEGORY_ID_OBJECTS); |
| addShownCategoryId(CATEGORY_ID_NATURE); |
| addShownCategoryId(CATEGORY_ID_PLACES); |
| mCurrentCategoryId = CATEGORY_ID_PEOPLE; |
| } else { |
| mCurrentCategoryId = CATEGORY_ID_SYMBOLS; |
| } |
| addShownCategoryId(CATEGORY_ID_SYMBOLS); |
| addShownCategoryId(CATEGORY_ID_EMOTICONS); |
| getKeyboard(CATEGORY_ID_RECENTS, 0 /* cagetoryPageId */) |
| .loadRecentKeys(mCategoryKeyboardMap.values()); |
| } |
| |
| private void addShownCategoryId(int categoryId) { |
| // Load a keyboard of categoryId |
| getKeyboard(categoryId, 0 /* cagetoryPageId */); |
| final CategoryProperties properties = |
| new CategoryProperties(categoryId, getCategoryPageCount(categoryId)); |
| mShownCategories.add(properties); |
| } |
| |
| public String getCategoryName(int categoryId, int categoryPageId) { |
| return sCategoryName[categoryId] + "-" + categoryPageId; |
| } |
| |
| public int getCategoryId(String name) { |
| final String[] strings = name.split("-"); |
| return mCategoryNameToIdMap.get(strings[0]); |
| } |
| |
| public int getCategoryIcon(int categoryId) { |
| return sCategoryIcon[categoryId]; |
| } |
| |
| public String getCategoryLabel(int categoryId) { |
| return sCategoryLabel[categoryId]; |
| } |
| |
| public ArrayList<CategoryProperties> getShownCategories() { |
| return mShownCategories; |
| } |
| |
| public int getCurrentCategoryId() { |
| return mCurrentCategoryId; |
| } |
| |
| public int getCurrentCategoryPageSize() { |
| return getCategoryPageSize(mCurrentCategoryId); |
| } |
| |
| public int getCategoryPageSize(int categoryId) { |
| for (final CategoryProperties prop : mShownCategories) { |
| if (prop.mCategoryId == categoryId) { |
| return prop.mPageCount; |
| } |
| } |
| Log.w(TAG, "Invalid category id: " + categoryId); |
| // Should not reach here. |
| return 0; |
| } |
| |
| public void setCurrentCategoryId(int categoryId) { |
| mCurrentCategoryId = categoryId; |
| } |
| |
| public void setCurrentCategoryPageId(int id) { |
| mCurrentCategoryPageId = id; |
| } |
| |
| public int getCurrentCategoryPageId() { |
| return mCurrentCategoryPageId; |
| } |
| |
| public void saveLastTypedCategoryPage() { |
| Settings.writeEmojiCategoryLastTypedId( |
| mPrefs, mCurrentCategoryId, mCurrentCategoryPageId); |
| } |
| |
| public boolean isInRecentTab() { |
| return mCurrentCategoryId == CATEGORY_ID_RECENTS; |
| } |
| |
| public int getTabIdFromCategoryId(int categoryId) { |
| for (int i = 0; i < mShownCategories.size(); ++i) { |
| if (mShownCategories.get(i).mCategoryId == categoryId) { |
| return i; |
| } |
| } |
| Log.w(TAG, "categoryId not found: " + categoryId); |
| return 0; |
| } |
| |
| // 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.mCategoryId == categoryId) { |
| return sum + lastSavedCategoryPageId; |
| } |
| sum += props.mPageCount; |
| } |
| Log.w(TAG, "categoryId not found: " + categoryId); |
| return 0; |
| } |
| |
| public int getRecentTabId() { |
| return getTabIdFromCategoryId(CATEGORY_ID_RECENTS); |
| } |
| |
| private int getCategoryPageCount(int categoryId) { |
| final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]); |
| return (keyboard.getKeys().length - 1) / mMaxPageKeyCount + 1; |
| } |
| |
| // Returns a pair of the category id and the category page id from the view pager's page |
| // position. The category page id is numbered in each category. And the view page position |
| // is the position of the current shown page in the view pager which contains all pages of |
| // all categories. |
| public Pair<Integer, Integer> getCategoryIdAndPageIdFromPagePosition(int position) { |
| int sum = 0; |
| for (CategoryProperties properties : mShownCategories) { |
| final int temp = sum; |
| sum += properties.mPageCount; |
| if (sum > position) { |
| return new Pair<Integer, Integer>(properties.mCategoryId, position - temp); |
| } |
| } |
| return null; |
| } |
| |
| // Returns a keyboard from the view pager's page position. |
| public DynamicGridKeyboard getKeyboardFromPagePosition(int position) { |
| final Pair<Integer, Integer> categoryAndId = |
| getCategoryIdAndPageIdFromPagePosition(position); |
| if (categoryAndId != null) { |
| return getKeyboard(categoryAndId.first, categoryAndId.second); |
| } |
| return null; |
| } |
| |
| public DynamicGridKeyboard getKeyboard(int categoryId, int id) { |
| synchronized(mCategoryKeyboardMap) { |
| final long key = (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id; |
| final DynamicGridKeyboard kbd; |
| if (!mCategoryKeyboardMap.containsKey(key)) { |
| if (categoryId != CATEGORY_ID_RECENTS) { |
| final Keyboard keyboard = |
| mLayoutSet.getKeyboard(sCategoryElementId[categoryId]); |
| final Key[][] sortedKeys = sortKeys(keyboard.getKeys(), mMaxPageKeyCount); |
| for (int i = 0; i < sortedKeys.length; ++i) { |
| final DynamicGridKeyboard tempKbd = new DynamicGridKeyboard(mPrefs, |
| mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS), |
| mMaxPageKeyCount, categoryId, i /* categoryPageId */); |
| for (Key emojiKey : sortedKeys[i]) { |
| if (emojiKey == null) { |
| break; |
| } |
| tempKbd.addKeyLast(emojiKey); |
| } |
| mCategoryKeyboardMap.put((((long) categoryId) |
| << Constants.MAX_INT_BIT_COUNT) | i, tempKbd); |
| } |
| kbd = mCategoryKeyboardMap.get(key); |
| } else { |
| kbd = new DynamicGridKeyboard(mPrefs, |
| mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS), |
| mMaxPageKeyCount, categoryId, 0 /* categoryPageId */); |
| mCategoryKeyboardMap.put(key, kbd); |
| } |
| } else { |
| kbd = mCategoryKeyboardMap.get(key); |
| } |
| return kbd; |
| } |
| } |
| |
| public int getTotalPageCountOfAllCategories() { |
| int sum = 0; |
| for (CategoryProperties properties : mShownCategories) { |
| sum += properties.mPageCount; |
| } |
| return sum; |
| } |
| |
| 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; |
| |
| public EmojiKeyboardView(final Context context, final AttributeSet attrs) { |
| this(context, attrs, R.attr.emojiKeyboardViewStyle); |
| } |
| |
| public EmojiKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { |
| super(context, attrs, defStyle); |
| final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs, |
| R.styleable.KeyboardView, defStyle, R.style.KeyboardView); |
| mKeyBackgroundId = keyboardViewAttr.getResourceId( |
| R.styleable.KeyboardView_keyBackground, 0); |
| mEmojiFunctionalKeyBackgroundId = keyboardViewAttr.getResourceId( |
| R.styleable.KeyboardView_keyBackgroundEmojiFunctional, 0); |
| keyboardViewAttr.recycle(); |
| final TypedArray emojiKeyboardViewAttr = context.obtainStyledAttributes(attrs, |
| R.styleable.EmojiKeyboardView, defStyle, R.style.EmojiKeyboardView); |
| mTabLabelColor = emojiKeyboardViewAttr.getColorStateList( |
| R.styleable.EmojiKeyboardView_emojiTabLabelColor); |
| emojiKeyboardViewAttr.recycle(); |
| final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( |
| context, null /* editorInfo */); |
| final Resources res = context.getResources(); |
| final EmojiLayoutParams emojiLp = new EmojiLayoutParams(res); |
| builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype()); |
| builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res), |
| emojiLp.mEmojiKeyboardHeight); |
| builder.setOptions(false, false, false /* lanuageSwitchKeyEnabled */); |
| mLayoutSet = builder.build(); |
| mEmojiCategory = new EmojiCategory(PreferenceManager.getDefaultSharedPreferences(context), |
| context.getResources(), builder.build()); |
| mDeleteKeyOnTouchListener = new DeleteKeyOnTouchListener(context); |
| } |
| |
| @Override |
| protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { |
| super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| final Resources res = getContext().getResources(); |
| // The main keyboard expands to the entire this {@link KeyboardView}. |
| final int width = ResourceUtils.getDefaultKeyboardWidth(res) |
| + getPaddingLeft() + getPaddingRight(); |
| final int height = ResourceUtils.getDefaultKeyboardHeight(res) |
| + res.getDimensionPixelSize(R.dimen.suggestions_strip_height) |
| + getPaddingTop() + getPaddingBottom(); |
| setMeasuredDimension(width, height); |
| } |
| |
| 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(categoryId) != 0) { |
| final ImageView iconView = (ImageView)LayoutInflater.from(getContext()).inflate( |
| R.layout.emoji_keyboard_tab_icon, null); |
| iconView.setImageResource(mEmojiCategory.getCategoryIcon(categoryId)); |
| tspec.setIndicator(iconView); |
| } |
| if (mEmojiCategory.getCategoryLabel(categoryId) != null) { |
| final TextView textView = (TextView)LayoutInflater.from(getContext()).inflate( |
| R.layout.emoji_keyboard_tab_label, null); |
| textView.setText(mEmojiCategory.getCategoryLabel(categoryId)); |
| textView.setTextColor(mTabLabelColor); |
| tspec.setIndicator(textView); |
| } |
| host.addTab(tspec); |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| mTabHost = (TabHost)findViewById(R.id.emoji_category_tabhost); |
| mTabHost.setup(); |
| for (final CategoryProperties properties : mEmojiCategory.getShownCategories()) { |
| addTab(mTabHost, properties.mCategoryId); |
| } |
| mTabHost.setOnTabChangedListener(this); |
| mTabHost.getTabWidget().setStripEnabled(true); |
| |
| mEmojiKeyboardAdapter = new EmojiKeyboardAdapter(mEmojiCategory, mLayoutSet, this); |
| |
| mEmojiPager = (ViewPager)findViewById(R.id.emoji_keyboard_pager); |
| mEmojiPager.setAdapter(mEmojiKeyboardAdapter); |
| mEmojiPager.setOnPageChangeListener(this); |
| mEmojiPager.setOffscreenPageLimit(0); |
| final Resources res = getResources(); |
| final EmojiLayoutParams emojiLp = new EmojiLayoutParams(res); |
| emojiLp.setPagerProperties(mEmojiPager); |
| |
| mEmojiCategoryPageIndicatorView = |
| (EmojiCategoryPageIndicatorView)findViewById(R.id.emoji_category_page_id_view); |
| emojiLp.setCategoryPageIdViewProperties(mEmojiCategoryPageIndicatorView); |
| |
| setCurrentCategoryId(mEmojiCategory.getCurrentCategoryId(), true /* force */); |
| |
| final LinearLayout actionBar = (LinearLayout)findViewById(R.id.emoji_action_bar); |
| emojiLp.setActionBarProperties(actionBar); |
| |
| final ImageView deleteKey = (ImageView)findViewById(R.id.emoji_keyboard_delete); |
| deleteKey.setTag(Constants.CODE_DELETE); |
| deleteKey.setOnTouchListener(mDeleteKeyOnTouchListener); |
| final ImageView alphabetKey = (ImageView)findViewById(R.id.emoji_keyboard_alphabet); |
| alphabetKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId); |
| alphabetKey.setTag(Constants.CODE_SWITCH_ALPHA_SYMBOL); |
| alphabetKey.setOnClickListener(this); |
| final ImageView spaceKey = (ImageView)findViewById(R.id.emoji_keyboard_space); |
| spaceKey.setBackgroundResource(mKeyBackgroundId); |
| spaceKey.setTag(Constants.CODE_SPACE); |
| spaceKey.setOnClickListener(this); |
| emojiLp.setKeyProperties(spaceKey); |
| final ImageView sendKey = (ImageView)findViewById(R.id.emoji_keyboard_send); |
| sendKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId); |
| sendKey.setTag(Constants.CODE_ENTER); |
| sendKey.setOnClickListener(this); |
| } |
| |
| @Override |
| public void onTabChanged(final String tabId) { |
| final int categoryId = mEmojiCategory.getCategoryId(tabId); |
| setCurrentCategoryId(categoryId, false /* force */); |
| updateEmojiCategoryPageIdView(); |
| } |
| |
| |
| @Override |
| public void onPageSelected(final int position) { |
| final Pair<Integer, Integer> newPos = |
| mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position); |
| setCurrentCategoryId(newPos.first /* categoryId */, false /* force */); |
| mEmojiCategory.setCurrentCategoryPageId(newPos.second /* categoryPageId */); |
| updateEmojiCategoryPageIdView(); |
| } |
| |
| @Override |
| public void onPageScrollStateChanged(final int state) { |
| // Ignore this message. Only want the actual page selected. |
| } |
| |
| @Override |
| public void onPageScrolled(final int position, final float positionOffset, |
| final int positionOffsetPixels) { |
| final Pair<Integer, Integer> newPos = |
| mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position); |
| final int newCategoryId = newPos.first; |
| final int newCategorySize = mEmojiCategory.getCategoryPageSize(newCategoryId); |
| final int currentCategoryId = mEmojiCategory.getCurrentCategoryId(); |
| final int currentCategoryPageId = mEmojiCategory.getCurrentCategoryPageId(); |
| final int currentCategorySize = mEmojiCategory.getCurrentCategoryPageSize(); |
| if (newCategoryId == currentCategoryId) { |
| mEmojiCategoryPageIndicatorView.setCategoryPageId( |
| newCategorySize, newPos.second, positionOffset); |
| } else if (newCategoryId > currentCategoryId) { |
| mEmojiCategoryPageIndicatorView.setCategoryPageId( |
| currentCategorySize, currentCategoryPageId, positionOffset); |
| } else if (newCategoryId < currentCategoryId) { |
| mEmojiCategoryPageIndicatorView.setCategoryPageId( |
| currentCategorySize, currentCategoryPageId, positionOffset - 1); |
| } |
| } |
| |
| @Override |
| public void onClick(final View v) { |
| if (v.getTag() instanceof Integer) { |
| final int code = (Integer)v.getTag(); |
| registerCode(code); |
| return; |
| } |
| } |
| |
| private void registerCode(final int code) { |
| mKeyboardActionListener.onPressKey(code, 0 /* repeatCount */, true /* isSinglePointer */); |
| mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE); |
| mKeyboardActionListener.onReleaseKey(code, false /* withSliding */); |
| } |
| |
| @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()); |
| return; |
| } |
| registerCode(code); |
| } |
| |
| public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { |
| // TODO: |
| } |
| |
| public void setKeyboardActionListener(final KeyboardActionListener listener) { |
| mKeyboardActionListener = listener; |
| mDeleteKeyOnTouchListener.setKeyboardActionListener(mKeyboardActionListener); |
| } |
| |
| private void updateEmojiCategoryPageIdView() { |
| if (mEmojiCategoryPageIndicatorView == null) { |
| return; |
| } |
| mEmojiCategoryPageIndicatorView.setCategoryPageId( |
| mEmojiCategory.getCurrentCategoryPageSize(), |
| mEmojiCategory.getCurrentCategoryPageId(), 0.0f /* offset */); |
| } |
| |
| private void setCurrentCategoryId(final int categoryId, final boolean force) { |
| if (mEmojiCategory.getCurrentCategoryId() == categoryId && !force) { |
| return; |
| } |
| |
| mEmojiCategory.setCurrentCategoryId(categoryId); |
| final int newTabId = mEmojiCategory.getTabIdFromCategoryId(categoryId); |
| final int newCategoryPageId = mEmojiCategory.getPageIdFromCategoryId(categoryId); |
| if (force || mEmojiCategory.getCategoryIdAndPageIdFromPagePosition( |
| mEmojiPager.getCurrentItem()).first != categoryId) { |
| mEmojiPager.setCurrentItem(newCategoryPageId, false /* smoothScroll */); |
| } |
| if (force || mTabHost.getCurrentTab() != newTabId) { |
| mTabHost.setCurrentTab(newTabId); |
| } |
| } |
| |
| private static class EmojiKeyboardAdapter extends PagerAdapter { |
| private final ScrollKeyboardView.OnKeyClickListener mListener; |
| private final DynamicGridKeyboard mRecentsKeyboard; |
| private final SparseArray<ScrollKeyboardView> mActiveKeyboardView = |
| CollectionUtils.newSparseArray(); |
| private final EmojiCategory mEmojiCategory; |
| private int mActivePosition = 0; |
| |
| public EmojiKeyboardAdapter(final EmojiCategory emojiCategory, |
| final KeyboardLayoutSet layoutSet, |
| final ScrollKeyboardView.OnKeyClickListener listener) { |
| mEmojiCategory = emojiCategory; |
| mListener = listener; |
| mRecentsKeyboard = mEmojiCategory.getKeyboard(CATEGORY_ID_RECENTS, 0); |
| } |
| |
| public void addRecentKey(final Key key) { |
| if (mEmojiCategory.isInRecentTab()) { |
| return; |
| } |
| mRecentsKeyboard.addKeyFirst(key); |
| final KeyboardView recentKeyboardView = |
| mActiveKeyboardView.get(mEmojiCategory.getRecentTabId()); |
| if (recentKeyboardView != null) { |
| recentKeyboardView.invalidateAllKeys(); |
| } |
| } |
| |
| @Override |
| public int getCount() { |
| return mEmojiCategory.getTotalPageCountOfAllCategories(); |
| } |
| |
| @Override |
| public void setPrimaryItem(final View container, final int position, final Object object) { |
| if (mActivePosition == position) { |
| return; |
| } |
| final ScrollKeyboardView oldKeyboardView = mActiveKeyboardView.get(mActivePosition); |
| if (oldKeyboardView != null) { |
| oldKeyboardView.releaseCurrentKey(); |
| oldKeyboardView.deallocateMemory(); |
| } |
| mActivePosition = position; |
| } |
| |
| @Override |
| public Object instantiateItem(final ViewGroup container, final int position) { |
| final Keyboard keyboard = |
| mEmojiCategory.getKeyboardFromPagePosition(position); |
| final LayoutInflater inflater = LayoutInflater.from(container.getContext()); |
| final View view = inflater.inflate( |
| R.layout.emoji_keyboard_page, container, false /* attachToRoot */); |
| final ScrollKeyboardView keyboardView = (ScrollKeyboardView)view.findViewById( |
| R.id.emoji_keyboard_page); |
| keyboardView.setKeyboard(keyboard); |
| keyboardView.setOnKeyClickListener(mListener); |
| final ScrollViewWithNotifier scrollView = (ScrollViewWithNotifier)view.findViewById( |
| R.id.emoji_keyboard_scroller); |
| keyboardView.setScrollView(scrollView); |
| container.addView(view); |
| mActiveKeyboardView.put(position, keyboardView); |
| return view; |
| } |
| |
| @Override |
| public boolean isViewFromObject(final View view, final Object object) { |
| return view == object; |
| } |
| |
| @Override |
| public void destroyItem(final ViewGroup container, final int position, |
| final Object object) { |
| final ScrollKeyboardView keyboardView = mActiveKeyboardView.get(position); |
| if (keyboardView != null) { |
| keyboardView.deallocateMemory(); |
| mActiveKeyboardView.remove(position); |
| } |
| container.removeView(keyboardView); |
| } |
| } |
| |
| // TODO: Do the same things done in PointerTracker |
| private static class DeleteKeyOnTouchListener implements OnTouchListener { |
| private static final long MAX_REPEAT_COUNT_TIME = 30 * DateUtils.SECOND_IN_MILLIS; |
| private final int mDeleteKeyPressedBackgroundColor; |
| private final long mKeyRepeatStartTimeout; |
| private final long mKeyRepeatInterval; |
| |
| public DeleteKeyOnTouchListener(Context context) { |
| final Resources res = context.getResources(); |
| mDeleteKeyPressedBackgroundColor = |
| res.getColor(R.color.emoji_key_pressed_background_color); |
| mKeyRepeatStartTimeout = res.getInteger(R.integer.config_key_repeat_start_timeout); |
| mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval); |
| } |
| |
| private KeyboardActionListener mKeyboardActionListener = |
| KeyboardActionListener.EMPTY_LISTENER; |
| private DummyRepeatKeyRepeatTimer mTimer; |
| |
| private synchronized void startRepeat() { |
| if (mTimer != null) { |
| abortRepeat(); |
| } |
| mTimer = new DummyRepeatKeyRepeatTimer(); |
| mTimer.start(); |
| } |
| |
| private synchronized void abortRepeat() { |
| mTimer.abort(); |
| mTimer = null; |
| } |
| |
| // TODO: Remove |
| // This function is mimicking the repeat code in PointerTracker. |
| // Specifically referring to PointerTracker#startRepeatKey and PointerTracker#onKeyRepeat. |
| private class DummyRepeatKeyRepeatTimer extends Thread { |
| public boolean mAborted = false; |
| |
| @Override |
| public void run() { |
| int timeCount = 0; |
| while (timeCount < MAX_REPEAT_COUNT_TIME && !mAborted) { |
| if (timeCount > mKeyRepeatStartTimeout) { |
| pressDelete(); |
| } |
| timeCount += mKeyRepeatInterval; |
| try { |
| Thread.sleep(mKeyRepeatInterval); |
| } catch (InterruptedException e) { |
| } |
| } |
| } |
| |
| public void abort() { |
| mAborted = true; |
| } |
| } |
| |
| public void pressDelete() { |
| mKeyboardActionListener.onPressKey( |
| Constants.CODE_DELETE, 0 /* repeatCount */, true /* isSinglePointer */); |
| mKeyboardActionListener.onCodeInput( |
| Constants.CODE_DELETE, NOT_A_COORDINATE, NOT_A_COORDINATE); |
| mKeyboardActionListener.onReleaseKey( |
| Constants.CODE_DELETE, false /* withSliding */); |
| } |
| |
| public void setKeyboardActionListener(KeyboardActionListener listener) { |
| mKeyboardActionListener = listener; |
| } |
| |
| @Override |
| public boolean onTouch(View v, MotionEvent event) { |
| switch(event.getAction()) { |
| case MotionEvent.ACTION_DOWN: |
| v.setBackgroundColor(mDeleteKeyPressedBackgroundColor); |
| pressDelete(); |
| startRepeat(); |
| return true; |
| case MotionEvent.ACTION_UP: |
| v.setBackgroundColor(0); |
| abortRepeat(); |
| return true; |
| } |
| return false; |
| } |
| } |
| } |