/*
* 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 palettes.
* The Emoji keyboard consists of group of views {@link R.layout#emoji_palettes_view}.
*
* - Emoji category tabs.
*
- Delete button.
*
- Emoji keyboard pages that can be scrolled by swiping horizontally or by selecting a tab.
*
- Back to main keyboard button and enter button.
*
* Because of the above reasons, this class doesn't extend {@link KeyboardView}.
*/
public final class EmojiPalettesView extends LinearLayout implements OnTabChangeListener,
ViewPager.OnPageChangeListener, View.OnClickListener,
ScrollKeyboardView.OnKeyClickListener {
private static final String TAG = EmojiPalettesView.class.getSimpleName();
private static final boolean DEBUG_PAGER = false;
private final int mKeyBackgroundId;
private final int mEmojiFunctionalKeyBackgroundId;
private final KeyboardLayoutSet mLayoutSet;
private final ColorStateList mTabLabelColor;
private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener;
private EmojiPalettesAdapter mEmojiPalettesAdapter;
private TabHost mTabHost;
private ViewPager mEmojiPager;
private int mCurrentPagerPosition = 0;
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 mCategoryNameToIdMap = CollectionUtils.newHashMap();
private final ArrayList mShownCategories =
CollectionUtils.newArrayList();
private final ConcurrentHashMap
mCategoryKeyboardMap = new ConcurrentHashMap();
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 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 getCategoryIdAndPageIdFromPagePosition(int position) {
int sum = 0;
for (CategoryProperties properties : mShownCategories) {
final int temp = sum;
sum += properties.mPageCount;
if (sum > position) {
return new Pair(properties.mCategoryId, position - temp);
}
}
return null;
}
// Returns a keyboard from the view pager's page position.
public DynamicGridKeyboard getKeyboardFromPagePosition(int position) {
final Pair 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() {
@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 EmojiPalettesView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.emojiPalettesViewStyle);
}
public EmojiPalettesView(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 emojiPalettesViewAttr = context.obtainStyledAttributes(attrs,
R.styleable.EmojiPalettesView, defStyle, R.style.EmojiPalettesView);
mTabLabelColor = emojiPalettesViewAttr.getColorStateList(
R.styleable.EmojiPalettesView_emojiTabLabelColor);
emojiPalettesViewAttr.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);
mEmojiPalettesAdapter = new EmojiPalettesAdapter(mEmojiCategory, mLayoutSet, this);
mEmojiPager = (ViewPager)findViewById(R.id.emoji_keyboard_pager);
mEmojiPager.setAdapter(mEmojiPalettesAdapter);
mEmojiPager.setOnPageChangeListener(this);
mEmojiPager.setOffscreenPageLimit(0);
mEmojiPager.setPersistentDrawingCache(ViewPager.PERSISTENT_NO_CACHE);
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 alphabetKey2 = (ImageView)findViewById(R.id.emoji_keyboard_alphabet2);
alphabetKey2.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
alphabetKey2.setTag(Constants.CODE_SWITCH_ALPHA_SYMBOL);
alphabetKey2.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 newPos =
mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position);
setCurrentCategoryId(newPos.first /* categoryId */, false /* force */);
mEmojiCategory.setCurrentCategoryPageId(newPos.second /* categoryPageId */);
updateEmojiCategoryPageIdView();
mCurrentPagerPosition = position;
}
@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 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) {
mEmojiPalettesAdapter.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 startEmojiPalettes() {
if (DEBUG_PAGER) {
Log.d(TAG, "allocate emoji palettes memory " + mCurrentPagerPosition);
}
mEmojiPager.setAdapter(mEmojiPalettesAdapter);
mEmojiPager.setCurrentItem(mCurrentPagerPosition);
}
public void stopEmojiPalettes() {
if (DEBUG_PAGER) {
Log.d(TAG, "deallocate emoji palettes memory");
}
mEmojiPalettesAdapter.flushPendingRecentKeys();
mEmojiPager.setAdapter(null);
}
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) {
final int oldCategoryId = mEmojiCategory.getCurrentCategoryId();
if (oldCategoryId == categoryId && !force) {
return;
}
if (oldCategoryId == CATEGORY_ID_RECENTS) {
// Needs to save pending updates for recent keys when we get out of the recents
// category because we don't want to move the recent emojis around while the user
// is in the recents category.
mEmojiPalettesAdapter.flushPendingRecentKeys();
}
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 EmojiPalettesAdapter extends PagerAdapter {
private final ScrollKeyboardView.OnKeyClickListener mListener;
private final DynamicGridKeyboard mRecentsKeyboard;
private final SparseArray mActiveKeyboardViews =
CollectionUtils.newSparseArray();
private final EmojiCategory mEmojiCategory;
private int mActivePosition = 0;
public EmojiPalettesAdapter(final EmojiCategory emojiCategory,
final KeyboardLayoutSet layoutSet,
final ScrollKeyboardView.OnKeyClickListener listener) {
mEmojiCategory = emojiCategory;
mListener = listener;
mRecentsKeyboard = mEmojiCategory.getKeyboard(CATEGORY_ID_RECENTS, 0);
}
public void flushPendingRecentKeys() {
mRecentsKeyboard.flushPendingRecentKeys();
final KeyboardView recentKeyboardView =
mActiveKeyboardViews.get(mEmojiCategory.getRecentTabId());
if (recentKeyboardView != null) {
recentKeyboardView.invalidateAllKeys();
}
}
public void addRecentKey(final Key key) {
if (mEmojiCategory.isInRecentTab()) {
mRecentsKeyboard.addPendingKey(key);
return;
}
mRecentsKeyboard.addKeyFirst(key);
final KeyboardView recentKeyboardView =
mActiveKeyboardViews.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 = mActiveKeyboardViews.get(mActivePosition);
if (oldKeyboardView != null) {
oldKeyboardView.releaseCurrentKey();
oldKeyboardView.deallocateMemory();
}
mActivePosition = position;
}
@Override
public Object instantiateItem(final ViewGroup container, final int position) {
if (DEBUG_PAGER) {
Log.d(TAG, "instantiate item: " + position);
}
final ScrollKeyboardView oldKeyboardView = mActiveKeyboardViews.get(position);
if (oldKeyboardView != null) {
oldKeyboardView.deallocateMemory();
// This may be redundant but wanted to be safer..
mActiveKeyboardViews.remove(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);
mActiveKeyboardViews.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) {
if (DEBUG_PAGER) {
Log.d(TAG, "destroy item: " + position + ", " + object.getClass().getSimpleName());
}
final ScrollKeyboardView keyboardView = mActiveKeyboardViews.get(position);
if (keyboardView != null) {
keyboardView.deallocateMemory();
mActiveKeyboardViews.remove(position);
}
if (object instanceof View) {
container.removeView((View)object);
} else {
Log.w(TAG, "Warning!!! Emoji palette may be leaking. " + object);
}
}
}
// 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 repeatCount = 1;
int timeCount = 0;
while (timeCount < MAX_REPEAT_COUNT_TIME && !mAborted) {
if (timeCount > mKeyRepeatStartTimeout) {
pressDelete(repeatCount);
}
timeCount += mKeyRepeatInterval;
++repeatCount;
try {
Thread.sleep(mKeyRepeatInterval);
} catch (InterruptedException e) {
}
}
}
public void abort() {
mAborted = true;
}
}
public void pressDelete(int repeatCount) {
mKeyboardActionListener.onPressKey(
Constants.CODE_DELETE, 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(0 /* repeatCount */);
startRepeat();
return true;
case MotionEvent.ACTION_UP:
v.setBackgroundColor(0);
abortRepeat();
return true;
}
return false;
}
}
}