From 9f4d62cc42ab66f72ecb23996ffc2f8b039c8c4a Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Wed, 14 May 2014 19:32:00 +0900 Subject: Create com.android.inputmethod.keyboard.emoji package Change-Id: Idd12c2fa4f5add7bb50bd7349ff9a74fffe82cfd --- .../keyboard/EmojiCategoryPageIndicatorView.java | 69 --- .../inputmethod/keyboard/EmojiPalettesView.java | 543 --------------------- .../inputmethod/keyboard/KeyboardSwitcher.java | 1 + .../keyboard/emoji/DynamicGridKeyboard.java | 265 ++++++++++ .../inputmethod/keyboard/emoji/EmojiCategory.java | 358 ++++++++++++++ .../emoji/EmojiCategoryPageIndicatorView.java | 68 +++ .../keyboard/emoji/EmojiLayoutParams.java | 92 ++++ .../keyboard/emoji/EmojiPageKeyboardView.java | 194 ++++++++ .../keyboard/emoji/EmojiPalettesAdapter.java | 146 ++++++ .../keyboard/emoji/EmojiPalettesView.java | 542 ++++++++++++++++++++ .../keyboard/internal/DynamicGridKeyboard.java | 266 ---------- .../keyboard/internal/EmojiCategory.java | 359 -------------- .../keyboard/internal/EmojiLayoutParams.java | 93 ---- .../keyboard/internal/EmojiPageKeyboardView.java | 196 -------- .../keyboard/internal/EmojiPalettesAdapter.java | 147 ------ 15 files changed, 1666 insertions(+), 1673 deletions(-) delete mode 100644 java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java delete mode 100644 java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java create mode 100644 java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java create mode 100644 java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java create mode 100644 java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java create mode 100644 java/src/com/android/inputmethod/keyboard/emoji/EmojiLayoutParams.java create mode 100644 java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java create mode 100644 java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java create mode 100644 java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java delete mode 100644 java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java delete mode 100644 java/src/com/android/inputmethod/keyboard/internal/EmojiCategory.java delete mode 100644 java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java delete mode 100644 java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java delete mode 100644 java/src/com/android/inputmethod/keyboard/internal/EmojiPalettesAdapter.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java b/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java deleted file mode 100644 index 9922f9024..000000000 --- a/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 com.android.inputmethod.latin.R; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.util.AttributeSet; -import android.widget.LinearLayout; - -//TODO: Move this class to com.android.inputmethod.emoji package. -public final class EmojiCategoryPageIndicatorView extends LinearLayout { - private static final float BOTTOM_MARGIN_RATIO = 1.0f; - private final Paint mPaint = new Paint(); - private int mCategoryPageSize = 0; - private int mCurrentCategoryPageId = 0; - private float mOffset = 0.0f; - - public EmojiCategoryPageIndicatorView(final Context context) { - this(context, null /* attrs */); - } - - public EmojiCategoryPageIndicatorView(final Context context, final AttributeSet attrs) { - super(context, attrs); - mPaint.setColor(context.getResources().getColor( - R.color.emoji_category_page_id_view_foreground)); - } - - public void setCategoryPageId(final int size, final int id, final float offset) { - mCategoryPageSize = size; - mCurrentCategoryPageId = id; - mOffset = offset; - invalidate(); - } - - @Override - protected void onDraw(final Canvas canvas) { - if (mCategoryPageSize <= 1) { - // If the category is not set yet or contains only one category, - // just clear and return. - canvas.drawColor(0); - return; - } - final float height = getHeight(); - final float width = getWidth(); - final float unitWidth = width / mCategoryPageSize; - final float left = unitWidth * mCurrentCategoryPageId + mOffset * unitWidth; - final float top = 0.0f; - final float right = left + unitWidth; - final float bottom = height * BOTTOM_MARGIN_RATIO; - canvas.drawRect(left, top, right, bottom, mPaint); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java deleted file mode 100644 index 55e8071c1..000000000 --- a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * 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.res.ColorStateList; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.Typeface; -import android.os.CountDownTimer; -import android.preference.PreferenceManager; -import android.support.v4.view.ViewPager; -import android.util.AttributeSet; -import android.util.Pair; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.widget.ImageButton; -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.EmojiCategory; -import com.android.inputmethod.keyboard.internal.EmojiLayoutParams; -import com.android.inputmethod.keyboard.internal.EmojiPageKeyboardView; -import com.android.inputmethod.keyboard.internal.EmojiPalettesAdapter; -import com.android.inputmethod.keyboard.internal.KeyDrawParams; -import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; -import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; -import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.SubtypeSwitcher; -import com.android.inputmethod.latin.utils.ResourceUtils; - -import java.util.concurrent.TimeUnit; - -/** - * View class to implement Emoji palettes. - * The Emoji keyboard consists of group of views {@link R.layout#emoji_palettes_view}. - *
    - *
  1. Emoji category tabs. - *
  2. Delete button. - *
  3. Emoji keyboard pages that can be scrolled by swiping horizontally or by selecting a tab. - *
  4. Back to main keyboard button and enter button. - *
- * Because of the above reasons, this class doesn't extend {@link KeyboardView}. - */ -// TODO: Move this class to com.android.inputmethod.emoji package. -public final class EmojiPalettesView extends LinearLayout implements OnTabChangeListener, - ViewPager.OnPageChangeListener, View.OnClickListener, View.OnTouchListener, - EmojiPageKeyboardView.OnKeyEventListener { - private final int mFunctionalKeyBackgroundId; - private final int mSpacebarBackgroundId; - private final ColorStateList mTabLabelColor; - private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener; - private EmojiPalettesAdapter mEmojiPalettesAdapter; - private final EmojiLayoutParams mEmojiLayoutParams; - - private ImageButton mDeleteKey; - private TextView mAlphabetKeyLeft; - private TextView mAlphabetKeyRight; - private ImageButton mSpacebar; - private TabHost mTabHost; - private ViewPager mEmojiPager; - private int mCurrentPagerPosition = 0; - private EmojiCategoryPageIndicatorView mEmojiCategoryPageIndicatorView; - - private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER; - - 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); - final int keyBackgroundId = keyboardViewAttr.getResourceId( - R.styleable.KeyboardView_keyBackground, 0); - mFunctionalKeyBackgroundId = keyboardViewAttr.getResourceId( - R.styleable.KeyboardView_functionalKeyBackground, keyBackgroundId); - mSpacebarBackgroundId = keyboardViewAttr.getResourceId( - R.styleable.KeyboardView_spacebarBackground, keyBackgroundId); - 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(); - mEmojiLayoutParams = new EmojiLayoutParams(res); - builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype()); - builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res), - mEmojiLayoutParams.mEmojiKeyboardHeight); - builder.setOptions(false /* shortcutImeEnabled */, false /* showsVoiceInputKey */, - false /* languageSwitchKeyEnabled */); - 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.config_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)); - iconView.setContentDescription(mEmojiCategory.getAccessibilityDescription(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.setTypeface(Typeface.DEFAULT_BOLD); - textView.setContentDescription(mEmojiCategory.getAccessibilityDescription(categoryId)); - if (mTabLabelColor != null) { - 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 EmojiCategory.CategoryProperties properties - : mEmojiCategory.getShownCategories()) { - addTab(mTabHost, properties.mCategoryId); - } - mTabHost.setOnTabChangedListener(this); - mTabHost.getTabWidget().setStripEnabled(true); - - mEmojiPalettesAdapter = new EmojiPalettesAdapter(mEmojiCategory, this); - - mEmojiPager = (ViewPager)findViewById(R.id.emoji_keyboard_pager); - mEmojiPager.setAdapter(mEmojiPalettesAdapter); - mEmojiPager.setOnPageChangeListener(this); - mEmojiPager.setOffscreenPageLimit(0); - mEmojiPager.setPersistentDrawingCache(PERSISTENT_NO_CACHE); - mEmojiLayoutParams.setPagerProperties(mEmojiPager); - - mEmojiCategoryPageIndicatorView = - (EmojiCategoryPageIndicatorView)findViewById(R.id.emoji_category_page_id_view); - mEmojiLayoutParams.setCategoryPageIdViewProperties(mEmojiCategoryPageIndicatorView); - - setCurrentCategoryId(mEmojiCategory.getCurrentCategoryId(), true /* force */); - - final LinearLayout actionBar = (LinearLayout)findViewById(R.id.emoji_action_bar); - mEmojiLayoutParams.setActionBarProperties(actionBar); - - // deleteKey depends only on OnTouchListener. - mDeleteKey = (ImageButton)findViewById(R.id.emoji_keyboard_delete); - mDeleteKey.setBackgroundResource(mFunctionalKeyBackgroundId); - mDeleteKey.setTag(Constants.CODE_DELETE); - mDeleteKey.setOnTouchListener(mDeleteKeyOnTouchListener); - - // {@link #mAlphabetKeyLeft}, {@link #mAlphabetKeyRight, and spaceKey depend on - // {@link View.OnClickListener} as well as {@link View.OnTouchListener}. - // {@link View.OnTouchListener} is used as the trigger of key-press, while - // {@link View.OnClickListener} is used as the trigger of key-release which does not occur - // if the event is canceled by moving off the finger from the view. - // The text on alphabet keys are set at - // {@link #startEmojiPalettes(String,int,float,Typeface)}. - mAlphabetKeyLeft = (TextView)findViewById(R.id.emoji_keyboard_alphabet_left); - mAlphabetKeyLeft.setBackgroundResource(mFunctionalKeyBackgroundId); - mAlphabetKeyLeft.setTag(Constants.CODE_ALPHA_FROM_EMOJI); - mAlphabetKeyLeft.setOnTouchListener(this); - mAlphabetKeyLeft.setOnClickListener(this); - mAlphabetKeyRight = (TextView)findViewById(R.id.emoji_keyboard_alphabet_right); - mAlphabetKeyRight.setBackgroundResource(mFunctionalKeyBackgroundId); - mAlphabetKeyRight.setTag(Constants.CODE_ALPHA_FROM_EMOJI); - mAlphabetKeyRight.setOnTouchListener(this); - mAlphabetKeyRight.setOnClickListener(this); - mSpacebar = (ImageButton)findViewById(R.id.emoji_keyboard_space); - mSpacebar.setBackgroundResource(mSpacebarBackgroundId); - mSpacebar.setTag(Constants.CODE_SPACE); - mSpacebar.setOnTouchListener(this); - mSpacebar.setOnClickListener(this); - mEmojiLayoutParams.setKeyProperties(mSpacebar); - } - - @Override - public boolean dispatchTouchEvent(final MotionEvent ev) { - // Add here to the stack trace to nail down the {@link IllegalArgumentException} exception - // in MotionEvent that sporadically happens. - // TODO: Remove this override method once the issue has been addressed. - return super.dispatchTouchEvent(ev); - } - - @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) { - mEmojiPalettesAdapter.onPageScrolled(); - 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); - } - } - - /** - * Called from {@link EmojiPageKeyboardView} through {@link android.view.View.OnTouchListener} - * interface to handle touch events from View-based elements such as the space bar. - * Note that this method is used only for observing {@link MotionEvent#ACTION_DOWN} to trigger - * {@link KeyboardActionListener#onPressKey}. {@link KeyboardActionListener#onReleaseKey} will - * be covered by {@link #onClick} as long as the event is not canceled. - */ - @Override - public boolean onTouch(final View v, final MotionEvent event) { - if (event.getActionMasked() != MotionEvent.ACTION_DOWN) { - return false; - } - final Object tag = v.getTag(); - if (!(tag instanceof Integer)) { - return false; - } - final int code = (Integer) tag; - mKeyboardActionListener.onPressKey( - code, 0 /* repeatCount */, true /* isSinglePointer */); - // It's important to return false here. Otherwise, {@link #onClick} and touch-down visual - // feedback stop working. - return false; - } - - /** - * Called from {@link EmojiPageKeyboardView} through {@link android.view.View.OnClickListener} - * interface to handle non-canceled touch-up events from View-based elements such as the space - * bar. - */ - @Override - public void onClick(View v) { - final Object tag = v.getTag(); - if (!(tag instanceof Integer)) { - return; - } - final int code = (Integer) tag; - mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE, - false /* isKeyRepeat */); - mKeyboardActionListener.onReleaseKey(code, false /* withSliding */); - } - - /** - * Called from {@link EmojiPageKeyboardView} through - * {@link com.android.inputmethod.keyboard.internal.EmojiPageKeyboardView.OnKeyEventListener} - * interface to handle touch events from non-View-based elements such as Emoji buttons. - */ - @Override - public void onPressKey(final Key key) { - final int code = key.getCode(); - mKeyboardActionListener.onPressKey(code, 0 /* repeatCount */, true /* isSinglePointer */); - } - - /** - * Called from {@link EmojiPageKeyboardView} through - * {@link com.android.inputmethod.keyboard.internal.EmojiPageKeyboardView.OnKeyEventListener} - * interface to handle touch events from non-View-based elements such as Emoji buttons. - */ - @Override - public void onReleaseKey(final Key key) { - mEmojiPalettesAdapter.addRecentKey(key); - mEmojiCategory.saveLastTypedCategoryPage(); - final int code = key.getCode(); - if (code == Constants.CODE_OUTPUT_TEXT) { - mKeyboardActionListener.onTextInput(key.getOutputText()); - } else { - mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE, - false /* isKeyRepeat */); - } - mKeyboardActionListener.onReleaseKey(code, false /* withSliding */); - } - - public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { - if (!enabled) return; - // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off? - setLayerType(LAYER_TYPE_HARDWARE, null); - } - - private static void setupAlphabetKey(final TextView alphabetKey, final String label, - final KeyDrawParams params) { - alphabetKey.setText(label); - alphabetKey.setTextColor(params.mTextColor); - alphabetKey.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mLabelSize); - alphabetKey.setTypeface(params.mTypeface); - } - - public void startEmojiPalettes(final String switchToAlphaLabel, - final KeyVisualAttributes keyVisualAttr, final KeyboardIconsSet iconSet) { - mDeleteKey.setImageDrawable(iconSet.getIconDrawable(KeyboardIconsSet.NAME_DELETE_KEY)); - mSpacebar.setImageDrawable(iconSet.getIconDrawable(KeyboardIconsSet.NAME_SPACE_KEY)); - final KeyDrawParams params = new KeyDrawParams(); - params.updateParams(mEmojiLayoutParams.getActionBarHeight(), keyVisualAttr); - setupAlphabetKey(mAlphabetKeyLeft, switchToAlphaLabel, params); - setupAlphabetKey(mAlphabetKeyRight, switchToAlphaLabel, params); - mEmojiPager.setAdapter(mEmojiPalettesAdapter); - mEmojiPager.setCurrentItem(mCurrentPagerPosition); - } - - public void stopEmojiPalettes() { - 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 == EmojiCategory.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 DeleteKeyOnTouchListener implements OnTouchListener { - static final long MAX_REPEAT_COUNT_TIME = TimeUnit.SECONDS.toMillis(30); - final int mDeleteKeyPressedBackgroundColor; - final long mKeyRepeatStartTimeout; - 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); - mTimer = new CountDownTimer(MAX_REPEAT_COUNT_TIME, mKeyRepeatInterval) { - @Override - public void onTick(long millisUntilFinished) { - final long elapsed = MAX_REPEAT_COUNT_TIME - millisUntilFinished; - if (elapsed < mKeyRepeatStartTimeout) { - return; - } - onKeyRepeat(); - } - @Override - public void onFinish() { - onKeyRepeat(); - } - }; - } - - /** Key-repeat state. */ - private static final int KEY_REPEAT_STATE_INITIALIZED = 0; - // The key is touched but auto key-repeat is not started yet. - private static final int KEY_REPEAT_STATE_KEY_DOWN = 1; - // At least one key-repeat event has already been triggered and the key is not released. - private static final int KEY_REPEAT_STATE_KEY_REPEAT = 2; - - private KeyboardActionListener mKeyboardActionListener = - KeyboardActionListener.EMPTY_LISTENER; - - // TODO: Do the same things done in PointerTracker - private final CountDownTimer mTimer; - private int mState = KEY_REPEAT_STATE_INITIALIZED; - private int mRepeatCount = 0; - - public void setKeyboardActionListener(final KeyboardActionListener listener) { - mKeyboardActionListener = listener; - } - - @Override - public boolean onTouch(final View v, final MotionEvent event) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - onTouchDown(v); - return true; - case MotionEvent.ACTION_MOVE: - final float x = event.getX(); - final float y = event.getY(); - if (x < 0.0f || v.getWidth() < x || y < 0.0f || v.getHeight() < y) { - // Stop generating key events once the finger moves away from the view area. - onTouchCanceled(v); - } - return true; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - onTouchUp(v); - return true; - } - return false; - } - - private void handleKeyDown() { - mKeyboardActionListener.onPressKey( - Constants.CODE_DELETE, mRepeatCount, true /* isSinglePointer */); - } - - private void handleKeyUp() { - mKeyboardActionListener.onCodeInput(Constants.CODE_DELETE, - NOT_A_COORDINATE, NOT_A_COORDINATE, false /* isKeyRepeat */); - mKeyboardActionListener.onReleaseKey( - Constants.CODE_DELETE, false /* withSliding */); - ++mRepeatCount; - } - - private void onTouchDown(final View v) { - mTimer.cancel(); - mRepeatCount = 0; - handleKeyDown(); - v.setBackgroundColor(mDeleteKeyPressedBackgroundColor); - mState = KEY_REPEAT_STATE_KEY_DOWN; - mTimer.start(); - } - - private void onTouchUp(final View v) { - mTimer.cancel(); - if (mState == KEY_REPEAT_STATE_KEY_DOWN) { - handleKeyUp(); - } - v.setBackgroundColor(Color.TRANSPARENT); - mState = KEY_REPEAT_STATE_INITIALIZED; - } - - private void onTouchCanceled(final View v) { - mTimer.cancel(); - v.setBackgroundColor(Color.TRANSPARENT); - mState = KEY_REPEAT_STATE_INITIALIZED; - } - - // Called by {@link #mTimer} in the UI thread as an auto key-repeat signal. - void onKeyRepeat() { - switch (mState) { - case KEY_REPEAT_STATE_INITIALIZED: - // Basically this should not happen. - break; - case KEY_REPEAT_STATE_KEY_DOWN: - // Do not call {@link #handleKeyDown} here because it has already been called - // in {@link #onTouchDown}. - handleKeyUp(); - mState = KEY_REPEAT_STATE_KEY_REPEAT; - break; - case KEY_REPEAT_STATE_KEY_REPEAT: - handleKeyDown(); - handleKeyUp(); - break; - } - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index ad8fc7344..fc9faa6e3 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -28,6 +28,7 @@ import android.view.inputmethod.EditorInfo; import com.android.inputmethod.compat.InputMethodServiceCompatUtils; import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException; +import com.android.inputmethod.keyboard.emoji.EmojiPalettesView; import com.android.inputmethod.keyboard.internal.KeyboardState; import com.android.inputmethod.keyboard.internal.KeyboardTextsSet; import com.android.inputmethod.latin.InputView; diff --git a/java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java new file mode 100644 index 000000000..c7a9025c0 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java @@ -0,0 +1,265 @@ +/* + * 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.emoji; + +import android.content.SharedPreferences; +import android.text.TextUtils; +import android.util.Log; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.latin.settings.Settings; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.JsonUtils; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * This is a Keyboard class where you can add keys dynamically shown in a grid layout + */ +final class DynamicGridKeyboard extends Keyboard { + private static final String TAG = DynamicGridKeyboard.class.getSimpleName(); + private static final int TEMPLATE_KEY_CODE_0 = 0x30; + private static final int TEMPLATE_KEY_CODE_1 = 0x31; + private final Object mLock = new Object(); + + private final SharedPreferences mPrefs; + private final int mHorizontalStep; + private final int mVerticalStep; + private final int mColumnsNum; + private final int mMaxKeyCount; + private final boolean mIsRecents; + private final ArrayDeque mGridKeys = CollectionUtils.newArrayDeque(); + private final ArrayDeque mPendingKeys = CollectionUtils.newArrayDeque(); + + private List mCachedGridKeys; + + public DynamicGridKeyboard(final SharedPreferences prefs, final Keyboard templateKeyboard, + final int maxKeyCount, final int categoryId) { + super(templateKeyboard); + final Key key0 = getTemplateKey(TEMPLATE_KEY_CODE_0); + final Key key1 = getTemplateKey(TEMPLATE_KEY_CODE_1); + mHorizontalStep = Math.abs(key1.getX() - key0.getX()); + mVerticalStep = key0.getHeight() + mVerticalGap; + mColumnsNum = mBaseWidth / mHorizontalStep; + mMaxKeyCount = maxKeyCount; + mIsRecents = categoryId == EmojiCategory.ID_RECENTS; + mPrefs = prefs; + } + + private Key getTemplateKey(final int code) { + for (final Key key : super.getSortedKeys()) { + if (key.getCode() == code) { + return key; + } + } + throw new RuntimeException("Can't find template key: code=" + code); + } + + public void addPendingKey(final Key usedKey) { + synchronized (mLock) { + mPendingKeys.addLast(usedKey); + } + } + + public void flushPendingRecentKeys() { + synchronized (mLock) { + while (!mPendingKeys.isEmpty()) { + addKey(mPendingKeys.pollFirst(), true); + } + saveRecentKeys(); + } + } + + public void addKeyFirst(final Key usedKey) { + addKey(usedKey, true); + if (mIsRecents) { + saveRecentKeys(); + } + } + + public void addKeyLast(final Key usedKey) { + addKey(usedKey, false); + } + + private void addKey(final Key usedKey, final boolean addFirst) { + if (usedKey == null) { + return; + } + synchronized (mLock) { + mCachedGridKeys = null; + final GridKey key = new GridKey(usedKey); + while (mGridKeys.remove(key)) { + // Remove duplicate keys. + } + if (addFirst) { + mGridKeys.addFirst(key); + } else { + mGridKeys.addLast(key); + } + while (mGridKeys.size() > mMaxKeyCount) { + mGridKeys.removeLast(); + } + int index = 0; + for (final GridKey gridKey : mGridKeys) { + final int keyX0 = getKeyX0(index); + final int keyY0 = getKeyY0(index); + final int keyX1 = getKeyX1(index); + final int keyY1 = getKeyY1(index); + gridKey.updateCoordinates(keyX0, keyY0, keyX1, keyY1); + index++; + } + } + } + + private void saveRecentKeys() { + final ArrayList keys = CollectionUtils.newArrayList(); + for (final Key key : mGridKeys) { + if (key.getOutputText() != null) { + keys.add(key.getOutputText()); + } else { + keys.add(key.getCode()); + } + } + final String jsonStr = JsonUtils.listToJsonStr(keys); + Settings.writeEmojiRecentKeys(mPrefs, jsonStr); + } + + private static Key getKeyByCode(final Collection keyboards, + final int code) { + for (final DynamicGridKeyboard keyboard : keyboards) { + for (final Key key : keyboard.getSortedKeys()) { + if (key.getCode() == code) { + return key; + } + } + } + return null; + } + + private static Key getKeyByOutputText(final Collection keyboards, + final String outputText) { + for (final DynamicGridKeyboard keyboard : keyboards) { + for (final Key key : keyboard.getSortedKeys()) { + if (outputText.equals(key.getOutputText())) { + return key; + } + } + } + return null; + } + + public void loadRecentKeys(final Collection keyboards) { + final String str = Settings.readEmojiRecentKeys(mPrefs); + final List keys = JsonUtils.jsonStrToList(str); + for (final Object o : keys) { + final Key key; + if (o instanceof Integer) { + final int code = (Integer)o; + key = getKeyByCode(keyboards, code); + } else if (o instanceof String) { + final String outputText = (String)o; + key = getKeyByOutputText(keyboards, outputText); + } else { + Log.w(TAG, "Invalid object: " + o); + continue; + } + addKeyLast(key); + } + } + + private int getKeyX0(final int index) { + final int column = index % mColumnsNum; + return column * mHorizontalStep; + } + + private int getKeyX1(final int index) { + final int column = index % mColumnsNum + 1; + return column * mHorizontalStep; + } + + private int getKeyY0(final int index) { + final int row = index / mColumnsNum; + return row * mVerticalStep + mVerticalGap / 2; + } + + private int getKeyY1(final int index) { + final int row = index / mColumnsNum + 1; + return row * mVerticalStep + mVerticalGap / 2; + } + + @Override + public List getSortedKeys() { + synchronized (mLock) { + if (mCachedGridKeys != null) { + return mCachedGridKeys; + } + final ArrayList cachedKeys = new ArrayList(mGridKeys); + mCachedGridKeys = Collections.unmodifiableList(cachedKeys); + return mCachedGridKeys; + } + } + + @Override + public List getNearestKeys(final int x, final int y) { + // TODO: Calculate the nearest key index in mGridKeys from x and y. + return getSortedKeys(); + } + + static final class GridKey extends Key { + private int mCurrentX; + private int mCurrentY; + + public GridKey(final Key originalKey) { + super(originalKey); + } + + public void updateCoordinates(final int x0, final int y0, final int x1, final int y1) { + mCurrentX = x0; + mCurrentY = y0; + getHitBox().set(x0, y0, x1, y1); + } + + @Override + public int getX() { + return mCurrentX; + } + + @Override + public int getY() { + return mCurrentY; + } + + @Override + public boolean equals(final Object o) { + if (!(o instanceof Key)) return false; + final Key key = (Key)o; + if (getCode() != key.getCode()) return false; + if (!TextUtils.equals(getLabel(), key.getLabel())) return false; + return TextUtils.equals(getOutputText(), key.getOutputText()); + } + + @Override + public String toString() { + return "GridKey: " + super.toString(); + } + } +} diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java new file mode 100644 index 000000000..dd0e3e838 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2014 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.emoji; + +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.graphics.Rect; +import android.os.Build; +import android.util.Log; +import android.util.Pair; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardId; +import com.android.inputmethod.keyboard.KeyboardLayoutSet; +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.settings.Settings; +import com.android.inputmethod.latin.utils.CollectionUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +final class EmojiCategory { + private final String TAG = EmojiCategory.class.getSimpleName(); + + private static final int ID_UNSPECIFIED = -1; + public static final int ID_RECENTS = 0; + private static final int ID_PEOPLE = 1; + private static final int ID_OBJECTS = 2; + private static final int ID_NATURE = 3; + private static final int ID_PLACES = 4; + private static final int ID_SYMBOLS = 5; + private static final int ID_EMOTICONS = 6; + + public final class CategoryProperties { + public final int mCategoryId; + public final int mPageCount; + public CategoryProperties(final int categoryId, final int pageCount) { + mCategoryId = categoryId; + mPageCount = pageCount; + } + } + + private static final String[] sCategoryName = { + "recents", + "people", + "objects", + "nature", + "places", + "symbols", + "emoticons" }; + + private static final int[] sCategoryIcon = { + 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[] sAccessibilityDescriptionResourceIdsForCategories = { + R.string.spoken_descrption_emoji_category_recents, + R.string.spoken_descrption_emoji_category_people, + R.string.spoken_descrption_emoji_category_objects, + R.string.spoken_descrption_emoji_category_nature, + R.string.spoken_descrption_emoji_category_places, + R.string.spoken_descrption_emoji_category_symbols, + R.string.spoken_descrption_emoji_category_emoticons }; + + 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 Resources mRes; + 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 = EmojiCategory.ID_UNSPECIFIED; + private int mCurrentCategoryPageId = 0; + + public EmojiCategory(final SharedPreferences prefs, final Resources res, + final KeyboardLayoutSet layoutSet) { + mPrefs = prefs; + mRes = res; + mMaxPageKeyCount = res.getInteger(R.integer.config_emoji_keyboard_max_page_key_count); + mLayoutSet = layoutSet; + for (int i = 0; i < sCategoryName.length; ++i) { + mCategoryNameToIdMap.put(sCategoryName[i], i); + } + addShownCategoryId(EmojiCategory.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(EmojiCategory.ID_PEOPLE); + addShownCategoryId(EmojiCategory.ID_OBJECTS); + addShownCategoryId(EmojiCategory.ID_NATURE); + addShownCategoryId(EmojiCategory.ID_PLACES); + mCurrentCategoryId = + Settings.readLastShownEmojiCategoryId(mPrefs, EmojiCategory.ID_PEOPLE); + } else { + mCurrentCategoryId = + Settings.readLastShownEmojiCategoryId(mPrefs, EmojiCategory.ID_SYMBOLS); + } + addShownCategoryId(EmojiCategory.ID_SYMBOLS); + addShownCategoryId(EmojiCategory.ID_EMOTICONS); + getKeyboard(EmojiCategory.ID_RECENTS, 0 /* cagetoryPageId */) + .loadRecentKeys(mCategoryKeyboardMap.values()); + } + + private void addShownCategoryId(final 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(final int categoryId, final int categoryPageId) { + return sCategoryName[categoryId] + "-" + categoryPageId; + } + + public int getCategoryId(final String name) { + final String[] strings = name.split("-"); + return mCategoryNameToIdMap.get(strings[0]); + } + + public int getCategoryIcon(final int categoryId) { + return sCategoryIcon[categoryId]; + } + + public String getCategoryLabel(final int categoryId) { + return sCategoryLabel[categoryId]; + } + + public String getAccessibilityDescription(final int categoryId) { + return mRes.getString(sAccessibilityDescriptionResourceIdsForCategories[categoryId]); + } + + public ArrayList getShownCategories() { + return mShownCategories; + } + + public int getCurrentCategoryId() { + return mCurrentCategoryId; + } + + public int getCurrentCategoryPageSize() { + return getCategoryPageSize(mCurrentCategoryId); + } + + public int getCategoryPageSize(final 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(final int categoryId) { + mCurrentCategoryId = categoryId; + Settings.writeLastShownEmojiCategoryId(mPrefs, categoryId); + } + + public void setCurrentCategoryPageId(final int id) { + mCurrentCategoryPageId = id; + } + + public int getCurrentCategoryPageId() { + return mCurrentCategoryPageId; + } + + public void saveLastTypedCategoryPage() { + Settings.writeLastTypedEmojiCategoryPageId( + mPrefs, mCurrentCategoryId, mCurrentCategoryPageId); + } + + public boolean isInRecentTab() { + return mCurrentCategoryId == EmojiCategory.ID_RECENTS; + } + + public int getTabIdFromCategoryId(final 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(final int categoryId) { + final int lastSavedCategoryPageId = + Settings.readLastTypedEmojiCategoryPageId(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(EmojiCategory.ID_RECENTS); + } + + private int getCategoryPageCount(final int categoryId) { + final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]); + return (keyboard.getSortedKeys().size() - 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(final int position) { + int sum = 0; + for (final 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(final int position) { + final Pair categoryAndId = + getCategoryIdAndPageIdFromPagePosition(position); + if (categoryAndId != null) { + return getKeyboard(categoryAndId.first, categoryAndId.second); + } + return null; + } + + private static final Long getCategoryKeyboardMapKey(final int categoryId, final int id) { + return (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id; + } + + public DynamicGridKeyboard getKeyboard(final int categoryId, final int id) { + synchronized (mCategoryKeyboardMap) { + final Long categotyKeyboardMapKey = getCategoryKeyboardMapKey(categoryId, id); + if (mCategoryKeyboardMap.containsKey(categotyKeyboardMapKey)) { + return mCategoryKeyboardMap.get(categotyKeyboardMapKey); + } + + if (categoryId == EmojiCategory.ID_RECENTS) { + final DynamicGridKeyboard kbd = new DynamicGridKeyboard(mPrefs, + mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS), + mMaxPageKeyCount, categoryId); + mCategoryKeyboardMap.put(categotyKeyboardMapKey, kbd); + return kbd; + } + + final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]); + final Key[][] sortedKeys = sortKeysIntoPages( + keyboard.getSortedKeys(), mMaxPageKeyCount); + for (int pageId = 0; pageId < sortedKeys.length; ++pageId) { + final DynamicGridKeyboard tempKeyboard = new DynamicGridKeyboard(mPrefs, + mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS), + mMaxPageKeyCount, categoryId); + for (final Key emojiKey : sortedKeys[pageId]) { + if (emojiKey == null) { + break; + } + tempKeyboard.addKeyLast(emojiKey); + } + mCategoryKeyboardMap.put( + getCategoryKeyboardMapKey(categoryId, pageId), tempKeyboard); + } + return mCategoryKeyboardMap.get(categotyKeyboardMapKey); + } + } + + public int getTotalPageCountOfAllCategories() { + int sum = 0; + for (CategoryProperties properties : mShownCategories) { + sum += properties.mPageCount; + } + return sum; + } + + private static Comparator EMOJI_KEY_COMPARATOR = new Comparator() { + @Override + public int compare(final Key lhs, final 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; + } + }; + + private static Key[][] sortKeysIntoPages(final List inKeys, final int maxPageCount) { + final ArrayList keys = CollectionUtils.newArrayList(inKeys); + Collections.sort(keys, EMOJI_KEY_COMPARATOR); + final int pageCount = (keys.size() - 1) / maxPageCount + 1; + final Key[][] retval = new Key[pageCount][maxPageCount]; + for (int i = 0; i < keys.size(); ++i) { + retval[i / maxPageCount][i % maxPageCount] = keys.get(i); + } + return retval; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java new file mode 100644 index 000000000..74cfd9b4b --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java @@ -0,0 +1,68 @@ +/* + * 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.emoji; + +import com.android.inputmethod.latin.R; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +public final class EmojiCategoryPageIndicatorView extends LinearLayout { + private static final float BOTTOM_MARGIN_RATIO = 1.0f; + private final Paint mPaint = new Paint(); + private int mCategoryPageSize = 0; + private int mCurrentCategoryPageId = 0; + private float mOffset = 0.0f; + + public EmojiCategoryPageIndicatorView(final Context context) { + this(context, null /* attrs */); + } + + public EmojiCategoryPageIndicatorView(final Context context, final AttributeSet attrs) { + super(context, attrs); + mPaint.setColor(context.getResources().getColor( + R.color.emoji_category_page_id_view_foreground)); + } + + public void setCategoryPageId(final int size, final int id, final float offset) { + mCategoryPageSize = size; + mCurrentCategoryPageId = id; + mOffset = offset; + invalidate(); + } + + @Override + protected void onDraw(final Canvas canvas) { + if (mCategoryPageSize <= 1) { + // If the category is not set yet or contains only one category, + // just clear and return. + canvas.drawColor(0); + return; + } + final float height = getHeight(); + final float width = getWidth(); + final float unitWidth = width / mCategoryPageSize; + final float left = unitWidth * mCurrentCategoryPageId + mOffset * unitWidth; + final float top = 0.0f; + final float right = left + unitWidth; + final float bottom = height * BOTTOM_MARGIN_RATIO; + canvas.drawRect(left, top, right, bottom, mPaint); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiLayoutParams.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiLayoutParams.java new file mode 100644 index 000000000..77c183a99 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiLayoutParams.java @@ -0,0 +1,92 @@ +/* + * 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.emoji; + +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.ResourceUtils; + +import android.content.res.Resources; +import android.support.v4.view.ViewPager; +import android.widget.ImageView; +import android.widget.LinearLayout; + +final class EmojiLayoutParams { + private static final int DEFAULT_KEYBOARD_ROWS = 4; + + public final int mEmojiPagerHeight; + private final int mEmojiPagerBottomMargin; + public final int mEmojiKeyboardHeight; + private final int mEmojiCategoryPageIdViewHeight; + public final int mEmojiActionBarHeight; + public final int mKeyVerticalGap; + private final int mKeyHorizontalGap; + private final int mBottomPadding; + private final int mTopPadding; + + public EmojiLayoutParams(final Resources res) { + final int defaultKeyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res); + final int defaultKeyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res); + mKeyVerticalGap = (int) res.getFraction(R.fraction.config_key_vertical_gap_holo, + defaultKeyboardHeight, defaultKeyboardHeight); + mBottomPadding = (int) res.getFraction(R.fraction.config_keyboard_bottom_padding_holo, + defaultKeyboardHeight, defaultKeyboardHeight); + mTopPadding = (int) res.getFraction(R.fraction.config_keyboard_top_padding_holo, + defaultKeyboardHeight, defaultKeyboardHeight); + mKeyHorizontalGap = (int) (res.getFraction(R.fraction.config_key_horizontal_gap_holo, + defaultKeyboardWidth, defaultKeyboardWidth)); + mEmojiCategoryPageIdViewHeight = + (int) (res.getDimension(R.dimen.config_emoji_category_page_id_height)); + final int baseheight = defaultKeyboardHeight - mBottomPadding - mTopPadding + + mKeyVerticalGap; + mEmojiActionBarHeight = baseheight / DEFAULT_KEYBOARD_ROWS + - (mKeyVerticalGap - mBottomPadding) / 2; + mEmojiPagerHeight = defaultKeyboardHeight - mEmojiActionBarHeight + - mEmojiCategoryPageIdViewHeight; + mEmojiPagerBottomMargin = 0; + mEmojiKeyboardHeight = mEmojiPagerHeight - mEmojiPagerBottomMargin - 1; + } + + public void setPagerProperties(final ViewPager vp) { + final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) vp.getLayoutParams(); + lp.height = mEmojiKeyboardHeight; + lp.bottomMargin = mEmojiPagerBottomMargin; + vp.setLayoutParams(lp); + } + + public void setCategoryPageIdViewProperties(final LinearLayout ll) { + final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams(); + lp.height = mEmojiCategoryPageIdViewHeight; + ll.setLayoutParams(lp); + } + + public int getActionBarHeight() { + return mEmojiActionBarHeight - mBottomPadding; + } + + public void setActionBarProperties(final LinearLayout ll) { + final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams(); + lp.height = getActionBarHeight(); + ll.setLayoutParams(lp); + } + + public void setKeyProperties(final ImageView ib) { + final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ib.getLayoutParams(); + lp.leftMargin = mKeyHorizontalGap / 2; + lp.rightMargin = mKeyHorizontalGap / 2; + ib.setLayoutParams(lp); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java new file mode 100644 index 000000000..d14ffeef9 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java @@ -0,0 +1,194 @@ +/* + * 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.emoji; + +import android.content.Context; +import android.os.Handler; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.MotionEvent; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.KeyDetector; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.latin.R; + +/** + * This is an extended {@link KeyboardView} class that hosts an emoji page keyboard. + * Multi-touch unsupported. No gesture support. + */ +// TODO: Implement key popup preview. +final class EmojiPageKeyboardView extends KeyboardView implements + GestureDetector.OnGestureListener { + private static final long KEY_PRESS_DELAY_TIME = 250; // msec + private static final long KEY_RELEASE_DELAY_TIME = 30; // msec + + public interface OnKeyEventListener { + public void onPressKey(Key key); + public void onReleaseKey(Key key); + } + + private static final OnKeyEventListener EMPTY_LISTENER = new OnKeyEventListener() { + @Override + public void onPressKey(final Key key) {} + @Override + public void onReleaseKey(final Key key) {} + }; + + private OnKeyEventListener mListener = EMPTY_LISTENER; + private final KeyDetector mKeyDetector = new KeyDetector(); + private final GestureDetector mGestureDetector; + + public EmojiPageKeyboardView(final Context context, final AttributeSet attrs) { + this(context, attrs, R.attr.keyboardViewStyle); + } + + public EmojiPageKeyboardView(final Context context, final AttributeSet attrs, + final int defStyle) { + super(context, attrs, defStyle); + mGestureDetector = new GestureDetector(context, this); + mGestureDetector.setIsLongpressEnabled(false /* isLongpressEnabled */); + mHandler = new Handler(); + } + + public void setOnKeyEventListener(final OnKeyEventListener listener) { + mListener = listener; + } + + /** + * {@inheritDoc} + */ + @Override + public void setKeyboard(final Keyboard keyboard) { + super.setKeyboard(keyboard); + mKeyDetector.setKeyboard(keyboard, 0 /* correctionX */, 0 /* correctionY */); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onTouchEvent(final MotionEvent e) { + if (mGestureDetector.onTouchEvent(e)) { + return true; + } + final Key key = getKey(e); + if (key != null && key != mCurrentKey) { + releaseCurrentKey(); + } + return true; + } + + // {@link GestureEnabler#OnGestureListener} methods. + private Key mCurrentKey; + private Runnable mPendingKeyDown; + private final Handler mHandler; + + private Key getKey(final MotionEvent e) { + final int index = e.getActionIndex(); + final int x = (int)e.getX(index); + final int y = (int)e.getY(index); + return mKeyDetector.detectHitKey(x, y); + } + + public void releaseCurrentKey() { + mHandler.removeCallbacks(mPendingKeyDown); + mPendingKeyDown = null; + final Key currentKey = mCurrentKey; + if (currentKey == null) { + return; + } + currentKey.onReleased(); + invalidateKey(currentKey); + mCurrentKey = null; + } + + @Override + public boolean onDown(final MotionEvent e) { + final Key key = getKey(e); + releaseCurrentKey(); + mCurrentKey = key; + if (key == null) { + return false; + } + // Do not trigger key-down effect right now in case this is actually a fling action. + mPendingKeyDown = new Runnable() { + @Override + public void run() { + mPendingKeyDown = null; + key.onPressed(); + invalidateKey(key); + mListener.onPressKey(key); + } + }; + mHandler.postDelayed(mPendingKeyDown, KEY_PRESS_DELAY_TIME); + return false; + } + + @Override + public void onShowPress(final MotionEvent e) { + // User feedback is done at {@link #onDown(MotionEvent)}. + } + + @Override + public boolean onSingleTapUp(final MotionEvent e) { + final Key key = getKey(e); + final Runnable pendingKeyDown = mPendingKeyDown; + final Key currentKey = mCurrentKey; + releaseCurrentKey(); + if (key == null) { + return false; + } + if (key == currentKey && pendingKeyDown != null) { + pendingKeyDown.run(); + // Trigger key-release event a little later so that a user can see visual feedback. + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + key.onReleased(); + invalidateKey(key); + mListener.onReleaseKey(key); + } + }, KEY_RELEASE_DELAY_TIME); + } else { + key.onReleased(); + invalidateKey(key); + mListener.onReleaseKey(key); + } + return true; + } + + @Override + public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX, + final float distanceY) { + releaseCurrentKey(); + return false; + } + + @Override + public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, + final float velocityY) { + releaseCurrentKey(); + return false; + } + + @Override + public void onLongPress(final MotionEvent e) { + // Long press detection of {@link #mGestureDetector} is disabled and not used. + } +} diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java new file mode 100644 index 000000000..52a4dde97 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2014 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.emoji; + +import android.support.v4.view.PagerAdapter; +import android.util.Log; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.CollectionUtils; + +final class EmojiPalettesAdapter extends PagerAdapter { + private static final String TAG = EmojiPalettesAdapter.class.getSimpleName(); + private static final boolean DEBUG_PAGER = false; + + private final EmojiPageKeyboardView.OnKeyEventListener 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 EmojiPageKeyboardView.OnKeyEventListener listener) { + mEmojiCategory = emojiCategory; + mListener = listener; + mRecentsKeyboard = mEmojiCategory.getKeyboard(EmojiCategory.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(); + } + } + + public void onPageScrolled() { + // Make sure the delayed key-down event (highlight effect and haptic feedback) will be + // canceled. + final EmojiPageKeyboardView currentKeyboardView = + mActiveKeyboardViews.get(mActivePosition); + if (currentKeyboardView != null) { + currentKeyboardView.releaseCurrentKey(); + } + } + + @Override + public int getCount() { + return mEmojiCategory.getTotalPageCountOfAllCategories(); + } + + @Override + public void setPrimaryItem(final ViewGroup container, final int position, + final Object object) { + if (mActivePosition == position) { + return; + } + final EmojiPageKeyboardView 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 EmojiPageKeyboardView 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 EmojiPageKeyboardView keyboardView = (EmojiPageKeyboardView)inflater.inflate( + R.layout.emoji_keyboard_page, container, false /* attachToRoot */); + keyboardView.setKeyboard(keyboard); + keyboardView.setOnKeyEventListener(mListener); + container.addView(keyboardView); + mActiveKeyboardViews.put(position, keyboardView); + return keyboardView; + } + + @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 EmojiPageKeyboardView 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); + } + } +} diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java new file mode 100644 index 000000000..3813c578a --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java @@ -0,0 +1,542 @@ +/* + * 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.emoji; + +import static com.android.inputmethod.latin.Constants.NOT_A_COORDINATE; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.Typeface; +import android.os.CountDownTimer; +import android.preference.PreferenceManager; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.util.Pair; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ImageButton; +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.Key; +import com.android.inputmethod.keyboard.KeyboardActionListener; +import com.android.inputmethod.keyboard.KeyboardLayoutSet; +import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.keyboard.internal.KeyDrawParams; +import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; +import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.SubtypeSwitcher; +import com.android.inputmethod.latin.utils.ResourceUtils; + +import java.util.concurrent.TimeUnit; + +/** + * View class to implement Emoji palettes. + * The Emoji keyboard consists of group of views {@link R.layout#emoji_palettes_view}. + *
    + *
  1. Emoji category tabs. + *
  2. Delete button. + *
  3. Emoji keyboard pages that can be scrolled by swiping horizontally or by selecting a tab. + *
  4. 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, View.OnTouchListener, + EmojiPageKeyboardView.OnKeyEventListener { + private final int mFunctionalKeyBackgroundId; + private final int mSpacebarBackgroundId; + private final ColorStateList mTabLabelColor; + private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener; + private EmojiPalettesAdapter mEmojiPalettesAdapter; + private final EmojiLayoutParams mEmojiLayoutParams; + + private ImageButton mDeleteKey; + private TextView mAlphabetKeyLeft; + private TextView mAlphabetKeyRight; + private ImageButton mSpacebar; + private TabHost mTabHost; + private ViewPager mEmojiPager; + private int mCurrentPagerPosition = 0; + private EmojiCategoryPageIndicatorView mEmojiCategoryPageIndicatorView; + + private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER; + + 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); + final int keyBackgroundId = keyboardViewAttr.getResourceId( + R.styleable.KeyboardView_keyBackground, 0); + mFunctionalKeyBackgroundId = keyboardViewAttr.getResourceId( + R.styleable.KeyboardView_functionalKeyBackground, keyBackgroundId); + mSpacebarBackgroundId = keyboardViewAttr.getResourceId( + R.styleable.KeyboardView_spacebarBackground, keyBackgroundId); + 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(); + mEmojiLayoutParams = new EmojiLayoutParams(res); + builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype()); + builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res), + mEmojiLayoutParams.mEmojiKeyboardHeight); + builder.setOptions(false /* shortcutImeEnabled */, false /* showsVoiceInputKey */, + false /* languageSwitchKeyEnabled */); + 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.config_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)); + iconView.setContentDescription(mEmojiCategory.getAccessibilityDescription(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.setTypeface(Typeface.DEFAULT_BOLD); + textView.setContentDescription(mEmojiCategory.getAccessibilityDescription(categoryId)); + if (mTabLabelColor != null) { + 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 EmojiCategory.CategoryProperties properties + : mEmojiCategory.getShownCategories()) { + addTab(mTabHost, properties.mCategoryId); + } + mTabHost.setOnTabChangedListener(this); + mTabHost.getTabWidget().setStripEnabled(true); + + mEmojiPalettesAdapter = new EmojiPalettesAdapter(mEmojiCategory, this); + + mEmojiPager = (ViewPager)findViewById(R.id.emoji_keyboard_pager); + mEmojiPager.setAdapter(mEmojiPalettesAdapter); + mEmojiPager.setOnPageChangeListener(this); + mEmojiPager.setOffscreenPageLimit(0); + mEmojiPager.setPersistentDrawingCache(PERSISTENT_NO_CACHE); + mEmojiLayoutParams.setPagerProperties(mEmojiPager); + + mEmojiCategoryPageIndicatorView = + (EmojiCategoryPageIndicatorView)findViewById(R.id.emoji_category_page_id_view); + mEmojiLayoutParams.setCategoryPageIdViewProperties(mEmojiCategoryPageIndicatorView); + + setCurrentCategoryId(mEmojiCategory.getCurrentCategoryId(), true /* force */); + + final LinearLayout actionBar = (LinearLayout)findViewById(R.id.emoji_action_bar); + mEmojiLayoutParams.setActionBarProperties(actionBar); + + // deleteKey depends only on OnTouchListener. + mDeleteKey = (ImageButton)findViewById(R.id.emoji_keyboard_delete); + mDeleteKey.setBackgroundResource(mFunctionalKeyBackgroundId); + mDeleteKey.setTag(Constants.CODE_DELETE); + mDeleteKey.setOnTouchListener(mDeleteKeyOnTouchListener); + + // {@link #mAlphabetKeyLeft}, {@link #mAlphabetKeyRight, and spaceKey depend on + // {@link View.OnClickListener} as well as {@link View.OnTouchListener}. + // {@link View.OnTouchListener} is used as the trigger of key-press, while + // {@link View.OnClickListener} is used as the trigger of key-release which does not occur + // if the event is canceled by moving off the finger from the view. + // The text on alphabet keys are set at + // {@link #startEmojiPalettes(String,int,float,Typeface)}. + mAlphabetKeyLeft = (TextView)findViewById(R.id.emoji_keyboard_alphabet_left); + mAlphabetKeyLeft.setBackgroundResource(mFunctionalKeyBackgroundId); + mAlphabetKeyLeft.setTag(Constants.CODE_ALPHA_FROM_EMOJI); + mAlphabetKeyLeft.setOnTouchListener(this); + mAlphabetKeyLeft.setOnClickListener(this); + mAlphabetKeyRight = (TextView)findViewById(R.id.emoji_keyboard_alphabet_right); + mAlphabetKeyRight.setBackgroundResource(mFunctionalKeyBackgroundId); + mAlphabetKeyRight.setTag(Constants.CODE_ALPHA_FROM_EMOJI); + mAlphabetKeyRight.setOnTouchListener(this); + mAlphabetKeyRight.setOnClickListener(this); + mSpacebar = (ImageButton)findViewById(R.id.emoji_keyboard_space); + mSpacebar.setBackgroundResource(mSpacebarBackgroundId); + mSpacebar.setTag(Constants.CODE_SPACE); + mSpacebar.setOnTouchListener(this); + mSpacebar.setOnClickListener(this); + mEmojiLayoutParams.setKeyProperties(mSpacebar); + } + + @Override + public boolean dispatchTouchEvent(final MotionEvent ev) { + // Add here to the stack trace to nail down the {@link IllegalArgumentException} exception + // in MotionEvent that sporadically happens. + // TODO: Remove this override method once the issue has been addressed. + return super.dispatchTouchEvent(ev); + } + + @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) { + mEmojiPalettesAdapter.onPageScrolled(); + 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); + } + } + + /** + * Called from {@link EmojiPageKeyboardView} through {@link android.view.View.OnTouchListener} + * interface to handle touch events from View-based elements such as the space bar. + * Note that this method is used only for observing {@link MotionEvent#ACTION_DOWN} to trigger + * {@link KeyboardActionListener#onPressKey}. {@link KeyboardActionListener#onReleaseKey} will + * be covered by {@link #onClick} as long as the event is not canceled. + */ + @Override + public boolean onTouch(final View v, final MotionEvent event) { + if (event.getActionMasked() != MotionEvent.ACTION_DOWN) { + return false; + } + final Object tag = v.getTag(); + if (!(tag instanceof Integer)) { + return false; + } + final int code = (Integer) tag; + mKeyboardActionListener.onPressKey( + code, 0 /* repeatCount */, true /* isSinglePointer */); + // It's important to return false here. Otherwise, {@link #onClick} and touch-down visual + // feedback stop working. + return false; + } + + /** + * Called from {@link EmojiPageKeyboardView} through {@link android.view.View.OnClickListener} + * interface to handle non-canceled touch-up events from View-based elements such as the space + * bar. + */ + @Override + public void onClick(View v) { + final Object tag = v.getTag(); + if (!(tag instanceof Integer)) { + return; + } + final int code = (Integer) tag; + mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE, + false /* isKeyRepeat */); + mKeyboardActionListener.onReleaseKey(code, false /* withSliding */); + } + + /** + * Called from {@link EmojiPageKeyboardView} through + * {@link com.android.inputmethod.keyboard.emoji.EmojiPageKeyboardView.OnKeyEventListener} + * interface to handle touch events from non-View-based elements such as Emoji buttons. + */ + @Override + public void onPressKey(final Key key) { + final int code = key.getCode(); + mKeyboardActionListener.onPressKey(code, 0 /* repeatCount */, true /* isSinglePointer */); + } + + /** + * Called from {@link EmojiPageKeyboardView} through + * {@link com.android.inputmethod.keyboard.emoji.EmojiPageKeyboardView.OnKeyEventListener} + * interface to handle touch events from non-View-based elements such as Emoji buttons. + */ + @Override + public void onReleaseKey(final Key key) { + mEmojiPalettesAdapter.addRecentKey(key); + mEmojiCategory.saveLastTypedCategoryPage(); + final int code = key.getCode(); + if (code == Constants.CODE_OUTPUT_TEXT) { + mKeyboardActionListener.onTextInput(key.getOutputText()); + } else { + mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE, + false /* isKeyRepeat */); + } + mKeyboardActionListener.onReleaseKey(code, false /* withSliding */); + } + + public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { + if (!enabled) return; + // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off? + setLayerType(LAYER_TYPE_HARDWARE, null); + } + + private static void setupAlphabetKey(final TextView alphabetKey, final String label, + final KeyDrawParams params) { + alphabetKey.setText(label); + alphabetKey.setTextColor(params.mTextColor); + alphabetKey.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mLabelSize); + alphabetKey.setTypeface(params.mTypeface); + } + + public void startEmojiPalettes(final String switchToAlphaLabel, + final KeyVisualAttributes keyVisualAttr, final KeyboardIconsSet iconSet) { + mDeleteKey.setImageDrawable(iconSet.getIconDrawable(KeyboardIconsSet.NAME_DELETE_KEY)); + mSpacebar.setImageDrawable(iconSet.getIconDrawable(KeyboardIconsSet.NAME_SPACE_KEY)); + final KeyDrawParams params = new KeyDrawParams(); + params.updateParams(mEmojiLayoutParams.getActionBarHeight(), keyVisualAttr); + setupAlphabetKey(mAlphabetKeyLeft, switchToAlphaLabel, params); + setupAlphabetKey(mAlphabetKeyRight, switchToAlphaLabel, params); + mEmojiPager.setAdapter(mEmojiPalettesAdapter); + mEmojiPager.setCurrentItem(mCurrentPagerPosition); + } + + public void stopEmojiPalettes() { + 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 == EmojiCategory.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 DeleteKeyOnTouchListener implements OnTouchListener { + static final long MAX_REPEAT_COUNT_TIME = TimeUnit.SECONDS.toMillis(30); + final int mDeleteKeyPressedBackgroundColor; + final long mKeyRepeatStartTimeout; + 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); + mTimer = new CountDownTimer(MAX_REPEAT_COUNT_TIME, mKeyRepeatInterval) { + @Override + public void onTick(long millisUntilFinished) { + final long elapsed = MAX_REPEAT_COUNT_TIME - millisUntilFinished; + if (elapsed < mKeyRepeatStartTimeout) { + return; + } + onKeyRepeat(); + } + @Override + public void onFinish() { + onKeyRepeat(); + } + }; + } + + /** Key-repeat state. */ + private static final int KEY_REPEAT_STATE_INITIALIZED = 0; + // The key is touched but auto key-repeat is not started yet. + private static final int KEY_REPEAT_STATE_KEY_DOWN = 1; + // At least one key-repeat event has already been triggered and the key is not released. + private static final int KEY_REPEAT_STATE_KEY_REPEAT = 2; + + private KeyboardActionListener mKeyboardActionListener = + KeyboardActionListener.EMPTY_LISTENER; + + // TODO: Do the same things done in PointerTracker + private final CountDownTimer mTimer; + private int mState = KEY_REPEAT_STATE_INITIALIZED; + private int mRepeatCount = 0; + + public void setKeyboardActionListener(final KeyboardActionListener listener) { + mKeyboardActionListener = listener; + } + + @Override + public boolean onTouch(final View v, final MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + onTouchDown(v); + return true; + case MotionEvent.ACTION_MOVE: + final float x = event.getX(); + final float y = event.getY(); + if (x < 0.0f || v.getWidth() < x || y < 0.0f || v.getHeight() < y) { + // Stop generating key events once the finger moves away from the view area. + onTouchCanceled(v); + } + return true; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + onTouchUp(v); + return true; + } + return false; + } + + private void handleKeyDown() { + mKeyboardActionListener.onPressKey( + Constants.CODE_DELETE, mRepeatCount, true /* isSinglePointer */); + } + + private void handleKeyUp() { + mKeyboardActionListener.onCodeInput(Constants.CODE_DELETE, + NOT_A_COORDINATE, NOT_A_COORDINATE, false /* isKeyRepeat */); + mKeyboardActionListener.onReleaseKey( + Constants.CODE_DELETE, false /* withSliding */); + ++mRepeatCount; + } + + private void onTouchDown(final View v) { + mTimer.cancel(); + mRepeatCount = 0; + handleKeyDown(); + v.setBackgroundColor(mDeleteKeyPressedBackgroundColor); + mState = KEY_REPEAT_STATE_KEY_DOWN; + mTimer.start(); + } + + private void onTouchUp(final View v) { + mTimer.cancel(); + if (mState == KEY_REPEAT_STATE_KEY_DOWN) { + handleKeyUp(); + } + v.setBackgroundColor(Color.TRANSPARENT); + mState = KEY_REPEAT_STATE_INITIALIZED; + } + + private void onTouchCanceled(final View v) { + mTimer.cancel(); + v.setBackgroundColor(Color.TRANSPARENT); + mState = KEY_REPEAT_STATE_INITIALIZED; + } + + // Called by {@link #mTimer} in the UI thread as an auto key-repeat signal. + void onKeyRepeat() { + switch (mState) { + case KEY_REPEAT_STATE_INITIALIZED: + // Basically this should not happen. + break; + case KEY_REPEAT_STATE_KEY_DOWN: + // Do not call {@link #handleKeyDown} here because it has already been called + // in {@link #onTouchDown}. + handleKeyUp(); + mState = KEY_REPEAT_STATE_KEY_REPEAT; + break; + case KEY_REPEAT_STATE_KEY_REPEAT: + handleKeyDown(); + handleKeyUp(); + break; + } + } + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java deleted file mode 100644 index a4879b852..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * 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.internal; - -import android.content.SharedPreferences; -import android.text.TextUtils; -import android.util.Log; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.utils.CollectionUtils; -import com.android.inputmethod.latin.utils.JsonUtils; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * This is a Keyboard class where you can add keys dynamically shown in a grid layout - */ -// TODO: Move this class to com.android.inputmethod.emoji package. -public class DynamicGridKeyboard extends Keyboard { - private static final String TAG = DynamicGridKeyboard.class.getSimpleName(); - private static final int TEMPLATE_KEY_CODE_0 = 0x30; - private static final int TEMPLATE_KEY_CODE_1 = 0x31; - private final Object mLock = new Object(); - - private final SharedPreferences mPrefs; - private final int mHorizontalStep; - private final int mVerticalStep; - private final int mColumnsNum; - private final int mMaxKeyCount; - private final boolean mIsRecents; - private final ArrayDeque mGridKeys = CollectionUtils.newArrayDeque(); - private final ArrayDeque mPendingKeys = CollectionUtils.newArrayDeque(); - - private List mCachedGridKeys; - - public DynamicGridKeyboard(final SharedPreferences prefs, final Keyboard templateKeyboard, - final int maxKeyCount, final int categoryId) { - super(templateKeyboard); - final Key key0 = getTemplateKey(TEMPLATE_KEY_CODE_0); - final Key key1 = getTemplateKey(TEMPLATE_KEY_CODE_1); - mHorizontalStep = Math.abs(key1.getX() - key0.getX()); - mVerticalStep = key0.getHeight() + mVerticalGap; - mColumnsNum = mBaseWidth / mHorizontalStep; - mMaxKeyCount = maxKeyCount; - mIsRecents = categoryId == EmojiCategory.ID_RECENTS; - mPrefs = prefs; - } - - private Key getTemplateKey(final int code) { - for (final Key key : super.getSortedKeys()) { - if (key.getCode() == code) { - return key; - } - } - throw new RuntimeException("Can't find template key: code=" + code); - } - - public void addPendingKey(final Key usedKey) { - synchronized (mLock) { - mPendingKeys.addLast(usedKey); - } - } - - public void flushPendingRecentKeys() { - synchronized (mLock) { - while (!mPendingKeys.isEmpty()) { - addKey(mPendingKeys.pollFirst(), true); - } - saveRecentKeys(); - } - } - - public void addKeyFirst(final Key usedKey) { - addKey(usedKey, true); - if (mIsRecents) { - saveRecentKeys(); - } - } - - public void addKeyLast(final Key usedKey) { - addKey(usedKey, false); - } - - private void addKey(final Key usedKey, final boolean addFirst) { - if (usedKey == null) { - return; - } - synchronized (mLock) { - mCachedGridKeys = null; - final GridKey key = new GridKey(usedKey); - while (mGridKeys.remove(key)) { - // Remove duplicate keys. - } - if (addFirst) { - mGridKeys.addFirst(key); - } else { - mGridKeys.addLast(key); - } - while (mGridKeys.size() > mMaxKeyCount) { - mGridKeys.removeLast(); - } - int index = 0; - for (final GridKey gridKey : mGridKeys) { - final int keyX0 = getKeyX0(index); - final int keyY0 = getKeyY0(index); - final int keyX1 = getKeyX1(index); - final int keyY1 = getKeyY1(index); - gridKey.updateCoordinates(keyX0, keyY0, keyX1, keyY1); - index++; - } - } - } - - private void saveRecentKeys() { - final ArrayList keys = CollectionUtils.newArrayList(); - for (final Key key : mGridKeys) { - if (key.getOutputText() != null) { - keys.add(key.getOutputText()); - } else { - keys.add(key.getCode()); - } - } - final String jsonStr = JsonUtils.listToJsonStr(keys); - Settings.writeEmojiRecentKeys(mPrefs, jsonStr); - } - - private static Key getKeyByCode(final Collection keyboards, - final int code) { - for (final DynamicGridKeyboard keyboard : keyboards) { - for (final Key key : keyboard.getSortedKeys()) { - if (key.getCode() == code) { - return key; - } - } - } - return null; - } - - private static Key getKeyByOutputText(final Collection keyboards, - final String outputText) { - for (final DynamicGridKeyboard keyboard : keyboards) { - for (final Key key : keyboard.getSortedKeys()) { - if (outputText.equals(key.getOutputText())) { - return key; - } - } - } - return null; - } - - public void loadRecentKeys(final Collection keyboards) { - final String str = Settings.readEmojiRecentKeys(mPrefs); - final List keys = JsonUtils.jsonStrToList(str); - for (final Object o : keys) { - final Key key; - if (o instanceof Integer) { - final int code = (Integer)o; - key = getKeyByCode(keyboards, code); - } else if (o instanceof String) { - final String outputText = (String)o; - key = getKeyByOutputText(keyboards, outputText); - } else { - Log.w(TAG, "Invalid object: " + o); - continue; - } - addKeyLast(key); - } - } - - private int getKeyX0(final int index) { - final int column = index % mColumnsNum; - return column * mHorizontalStep; - } - - private int getKeyX1(final int index) { - final int column = index % mColumnsNum + 1; - return column * mHorizontalStep; - } - - private int getKeyY0(final int index) { - final int row = index / mColumnsNum; - return row * mVerticalStep + mVerticalGap / 2; - } - - private int getKeyY1(final int index) { - final int row = index / mColumnsNum + 1; - return row * mVerticalStep + mVerticalGap / 2; - } - - @Override - public List getSortedKeys() { - synchronized (mLock) { - if (mCachedGridKeys != null) { - return mCachedGridKeys; - } - final ArrayList cachedKeys = new ArrayList(mGridKeys); - mCachedGridKeys = Collections.unmodifiableList(cachedKeys); - return mCachedGridKeys; - } - } - - @Override - public List getNearestKeys(final int x, final int y) { - // TODO: Calculate the nearest key index in mGridKeys from x and y. - return getSortedKeys(); - } - - static final class GridKey extends Key { - private int mCurrentX; - private int mCurrentY; - - public GridKey(final Key originalKey) { - super(originalKey); - } - - public void updateCoordinates(final int x0, final int y0, final int x1, final int y1) { - mCurrentX = x0; - mCurrentY = y0; - getHitBox().set(x0, y0, x1, y1); - } - - @Override - public int getX() { - return mCurrentX; - } - - @Override - public int getY() { - return mCurrentY; - } - - @Override - public boolean equals(final Object o) { - if (!(o instanceof Key)) return false; - final Key key = (Key)o; - if (getCode() != key.getCode()) return false; - if (!TextUtils.equals(getLabel(), key.getLabel())) return false; - return TextUtils.equals(getOutputText(), key.getOutputText()); - } - - @Override - public String toString() { - return "GridKey: " + super.toString(); - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/EmojiCategory.java b/java/src/com/android/inputmethod/keyboard/internal/EmojiCategory.java deleted file mode 100644 index 10bd621e5..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/EmojiCategory.java +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright (C) 2014 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.internal; - -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.graphics.Rect; -import android.os.Build; -import android.util.Log; -import android.util.Pair; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardId; -import com.android.inputmethod.keyboard.KeyboardLayoutSet; -import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.utils.CollectionUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; - -// TODO: Move this class to com.android.inputmethod.emoji package. -public final class EmojiCategory { - private final String TAG = EmojiCategory.class.getSimpleName(); - - private static final int ID_UNSPECIFIED = -1; - public static final int ID_RECENTS = 0; - private static final int ID_PEOPLE = 1; - private static final int ID_OBJECTS = 2; - private static final int ID_NATURE = 3; - private static final int ID_PLACES = 4; - private static final int ID_SYMBOLS = 5; - private static final int ID_EMOTICONS = 6; - - public final class CategoryProperties { - public final int mCategoryId; - public final int mPageCount; - public CategoryProperties(final int categoryId, final int pageCount) { - mCategoryId = categoryId; - mPageCount = pageCount; - } - } - - private static final String[] sCategoryName = { - "recents", - "people", - "objects", - "nature", - "places", - "symbols", - "emoticons" }; - - private static final int[] sCategoryIcon = { - 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[] sAccessibilityDescriptionResourceIdsForCategories = { - R.string.spoken_descrption_emoji_category_recents, - R.string.spoken_descrption_emoji_category_people, - R.string.spoken_descrption_emoji_category_objects, - R.string.spoken_descrption_emoji_category_nature, - R.string.spoken_descrption_emoji_category_places, - R.string.spoken_descrption_emoji_category_symbols, - R.string.spoken_descrption_emoji_category_emoticons }; - - 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 Resources mRes; - 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 = EmojiCategory.ID_UNSPECIFIED; - private int mCurrentCategoryPageId = 0; - - public EmojiCategory(final SharedPreferences prefs, final Resources res, - final KeyboardLayoutSet layoutSet) { - mPrefs = prefs; - mRes = res; - mMaxPageKeyCount = res.getInteger(R.integer.config_emoji_keyboard_max_page_key_count); - mLayoutSet = layoutSet; - for (int i = 0; i < sCategoryName.length; ++i) { - mCategoryNameToIdMap.put(sCategoryName[i], i); - } - addShownCategoryId(EmojiCategory.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(EmojiCategory.ID_PEOPLE); - addShownCategoryId(EmojiCategory.ID_OBJECTS); - addShownCategoryId(EmojiCategory.ID_NATURE); - addShownCategoryId(EmojiCategory.ID_PLACES); - mCurrentCategoryId = - Settings.readLastShownEmojiCategoryId(mPrefs, EmojiCategory.ID_PEOPLE); - } else { - mCurrentCategoryId = - Settings.readLastShownEmojiCategoryId(mPrefs, EmojiCategory.ID_SYMBOLS); - } - addShownCategoryId(EmojiCategory.ID_SYMBOLS); - addShownCategoryId(EmojiCategory.ID_EMOTICONS); - getKeyboard(EmojiCategory.ID_RECENTS, 0 /* cagetoryPageId */) - .loadRecentKeys(mCategoryKeyboardMap.values()); - } - - private void addShownCategoryId(final 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(final int categoryId, final int categoryPageId) { - return sCategoryName[categoryId] + "-" + categoryPageId; - } - - public int getCategoryId(final String name) { - final String[] strings = name.split("-"); - return mCategoryNameToIdMap.get(strings[0]); - } - - public int getCategoryIcon(final int categoryId) { - return sCategoryIcon[categoryId]; - } - - public String getCategoryLabel(final int categoryId) { - return sCategoryLabel[categoryId]; - } - - public String getAccessibilityDescription(final int categoryId) { - return mRes.getString(sAccessibilityDescriptionResourceIdsForCategories[categoryId]); - } - - public ArrayList getShownCategories() { - return mShownCategories; - } - - public int getCurrentCategoryId() { - return mCurrentCategoryId; - } - - public int getCurrentCategoryPageSize() { - return getCategoryPageSize(mCurrentCategoryId); - } - - public int getCategoryPageSize(final 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(final int categoryId) { - mCurrentCategoryId = categoryId; - Settings.writeLastShownEmojiCategoryId(mPrefs, categoryId); - } - - public void setCurrentCategoryPageId(final int id) { - mCurrentCategoryPageId = id; - } - - public int getCurrentCategoryPageId() { - return mCurrentCategoryPageId; - } - - public void saveLastTypedCategoryPage() { - Settings.writeLastTypedEmojiCategoryPageId( - mPrefs, mCurrentCategoryId, mCurrentCategoryPageId); - } - - public boolean isInRecentTab() { - return mCurrentCategoryId == EmojiCategory.ID_RECENTS; - } - - public int getTabIdFromCategoryId(final 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(final int categoryId) { - final int lastSavedCategoryPageId = - Settings.readLastTypedEmojiCategoryPageId(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(EmojiCategory.ID_RECENTS); - } - - private int getCategoryPageCount(final int categoryId) { - final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]); - return (keyboard.getSortedKeys().size() - 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(final int position) { - int sum = 0; - for (final 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(final int position) { - final Pair categoryAndId = - getCategoryIdAndPageIdFromPagePosition(position); - if (categoryAndId != null) { - return getKeyboard(categoryAndId.first, categoryAndId.second); - } - return null; - } - - private static final Long getCategoryKeyboardMapKey(final int categoryId, final int id) { - return (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id; - } - - public DynamicGridKeyboard getKeyboard(final int categoryId, final int id) { - synchronized (mCategoryKeyboardMap) { - final Long categotyKeyboardMapKey = getCategoryKeyboardMapKey(categoryId, id); - if (mCategoryKeyboardMap.containsKey(categotyKeyboardMapKey)) { - return mCategoryKeyboardMap.get(categotyKeyboardMapKey); - } - - if (categoryId == EmojiCategory.ID_RECENTS) { - final DynamicGridKeyboard kbd = new DynamicGridKeyboard(mPrefs, - mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS), - mMaxPageKeyCount, categoryId); - mCategoryKeyboardMap.put(categotyKeyboardMapKey, kbd); - return kbd; - } - - final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]); - final Key[][] sortedKeys = sortKeysIntoPages( - keyboard.getSortedKeys(), mMaxPageKeyCount); - for (int pageId = 0; pageId < sortedKeys.length; ++pageId) { - final DynamicGridKeyboard tempKeyboard = new DynamicGridKeyboard(mPrefs, - mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS), - mMaxPageKeyCount, categoryId); - for (final Key emojiKey : sortedKeys[pageId]) { - if (emojiKey == null) { - break; - } - tempKeyboard.addKeyLast(emojiKey); - } - mCategoryKeyboardMap.put( - getCategoryKeyboardMapKey(categoryId, pageId), tempKeyboard); - } - return mCategoryKeyboardMap.get(categotyKeyboardMapKey); - } - } - - public int getTotalPageCountOfAllCategories() { - int sum = 0; - for (CategoryProperties properties : mShownCategories) { - sum += properties.mPageCount; - } - return sum; - } - - private static Comparator EMOJI_KEY_COMPARATOR = new Comparator() { - @Override - public int compare(final Key lhs, final 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; - } - }; - - private static Key[][] sortKeysIntoPages(final List inKeys, final int maxPageCount) { - final ArrayList keys = CollectionUtils.newArrayList(inKeys); - Collections.sort(keys, EMOJI_KEY_COMPARATOR); - final int pageCount = (keys.size() - 1) / maxPageCount + 1; - final Key[][] retval = new Key[pageCount][maxPageCount]; - for (int i = 0; i < keys.size(); ++i) { - retval[i / maxPageCount][i % maxPageCount] = keys.get(i); - } - return retval; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java b/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java deleted file mode 100644 index 78af66b9a..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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.internal; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.ResourceUtils; - -import android.content.res.Resources; -import android.support.v4.view.ViewPager; -import android.widget.ImageView; -import android.widget.LinearLayout; - -//TODO: Move this class to com.android.inputmethod.emoji package. -public final class EmojiLayoutParams { - private static final int DEFAULT_KEYBOARD_ROWS = 4; - - public final int mEmojiPagerHeight; - private final int mEmojiPagerBottomMargin; - public final int mEmojiKeyboardHeight; - private final int mEmojiCategoryPageIdViewHeight; - public final int mEmojiActionBarHeight; - public final int mKeyVerticalGap; - private final int mKeyHorizontalGap; - private final int mBottomPadding; - private final int mTopPadding; - - public EmojiLayoutParams(final Resources res) { - final int defaultKeyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res); - final int defaultKeyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res); - mKeyVerticalGap = (int) res.getFraction(R.fraction.config_key_vertical_gap_holo, - defaultKeyboardHeight, defaultKeyboardHeight); - mBottomPadding = (int) res.getFraction(R.fraction.config_keyboard_bottom_padding_holo, - defaultKeyboardHeight, defaultKeyboardHeight); - mTopPadding = (int) res.getFraction(R.fraction.config_keyboard_top_padding_holo, - defaultKeyboardHeight, defaultKeyboardHeight); - mKeyHorizontalGap = (int) (res.getFraction(R.fraction.config_key_horizontal_gap_holo, - defaultKeyboardWidth, defaultKeyboardWidth)); - mEmojiCategoryPageIdViewHeight = - (int) (res.getDimension(R.dimen.config_emoji_category_page_id_height)); - final int baseheight = defaultKeyboardHeight - mBottomPadding - mTopPadding - + mKeyVerticalGap; - mEmojiActionBarHeight = baseheight / DEFAULT_KEYBOARD_ROWS - - (mKeyVerticalGap - mBottomPadding) / 2; - mEmojiPagerHeight = defaultKeyboardHeight - mEmojiActionBarHeight - - mEmojiCategoryPageIdViewHeight; - mEmojiPagerBottomMargin = 0; - mEmojiKeyboardHeight = mEmojiPagerHeight - mEmojiPagerBottomMargin - 1; - } - - public void setPagerProperties(final ViewPager vp) { - final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) vp.getLayoutParams(); - lp.height = mEmojiKeyboardHeight; - lp.bottomMargin = mEmojiPagerBottomMargin; - vp.setLayoutParams(lp); - } - - public void setCategoryPageIdViewProperties(final LinearLayout ll) { - final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams(); - lp.height = mEmojiCategoryPageIdViewHeight; - ll.setLayoutParams(lp); - } - - public int getActionBarHeight() { - return mEmojiActionBarHeight - mBottomPadding; - } - - public void setActionBarProperties(final LinearLayout ll) { - final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams(); - lp.height = getActionBarHeight(); - ll.setLayoutParams(lp); - } - - public void setKeyProperties(final ImageView ib) { - final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ib.getLayoutParams(); - lp.leftMargin = mKeyHorizontalGap / 2; - lp.rightMargin = mKeyHorizontalGap / 2; - ib.setLayoutParams(lp); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java b/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java deleted file mode 100644 index 2f67d194e..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 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.internal; - -import android.content.Context; -import android.os.Handler; -import android.util.AttributeSet; -import android.view.GestureDetector; -import android.view.MotionEvent; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.KeyDetector; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardView; -import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.latin.R; - -/** - * This is an extended {@link KeyboardView} class that hosts an emoji page keyboard. - * Multi-touch unsupported. No {@link PointerTracker}s. No gesture support. - */ -// TODO: Move this class to com.android.inputmethod.emoji package. -// TODO: Implement key popup preview. -public final class EmojiPageKeyboardView extends KeyboardView implements - GestureDetector.OnGestureListener { - private static final long KEY_PRESS_DELAY_TIME = 250; // msec - private static final long KEY_RELEASE_DELAY_TIME = 30; // msec - - public interface OnKeyEventListener { - public void onPressKey(Key key); - public void onReleaseKey(Key key); - } - - private static final OnKeyEventListener EMPTY_LISTENER = new OnKeyEventListener() { - @Override - public void onPressKey(final Key key) {} - @Override - public void onReleaseKey(final Key key) {} - }; - - private OnKeyEventListener mListener = EMPTY_LISTENER; - private final KeyDetector mKeyDetector = new KeyDetector(); - private final GestureDetector mGestureDetector; - - public EmojiPageKeyboardView(final Context context, final AttributeSet attrs) { - this(context, attrs, R.attr.keyboardViewStyle); - } - - public EmojiPageKeyboardView(final Context context, final AttributeSet attrs, - final int defStyle) { - super(context, attrs, defStyle); - mGestureDetector = new GestureDetector(context, this); - mGestureDetector.setIsLongpressEnabled(false /* isLongpressEnabled */); - mHandler = new Handler(); - } - - public void setOnKeyEventListener(final OnKeyEventListener listener) { - mListener = listener; - } - - /** - * {@inheritDoc} - */ - @Override - public void setKeyboard(final Keyboard keyboard) { - super.setKeyboard(keyboard); - mKeyDetector.setKeyboard(keyboard, 0 /* correctionX */, 0 /* correctionY */); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean onTouchEvent(final MotionEvent e) { - if (mGestureDetector.onTouchEvent(e)) { - return true; - } - final Key key = getKey(e); - if (key != null && key != mCurrentKey) { - releaseCurrentKey(); - } - return true; - } - - // {@link GestureEnabler#OnGestureListener} methods. - private Key mCurrentKey; - private Runnable mPendingKeyDown; - private final Handler mHandler; - - private Key getKey(final MotionEvent e) { - final int index = e.getActionIndex(); - final int x = (int)e.getX(index); - final int y = (int)e.getY(index); - return mKeyDetector.detectHitKey(x, y); - } - - public void releaseCurrentKey() { - mHandler.removeCallbacks(mPendingKeyDown); - mPendingKeyDown = null; - final Key currentKey = mCurrentKey; - if (currentKey == null) { - return; - } - currentKey.onReleased(); - invalidateKey(currentKey); - mCurrentKey = null; - } - - @Override - public boolean onDown(final MotionEvent e) { - final Key key = getKey(e); - releaseCurrentKey(); - mCurrentKey = key; - if (key == null) { - return false; - } - // Do not trigger key-down effect right now in case this is actually a fling action. - mPendingKeyDown = new Runnable() { - @Override - public void run() { - mPendingKeyDown = null; - key.onPressed(); - invalidateKey(key); - mListener.onPressKey(key); - } - }; - mHandler.postDelayed(mPendingKeyDown, KEY_PRESS_DELAY_TIME); - return false; - } - - @Override - public void onShowPress(final MotionEvent e) { - // User feedback is done at {@link #onDown(MotionEvent)}. - } - - @Override - public boolean onSingleTapUp(final MotionEvent e) { - final Key key = getKey(e); - final Runnable pendingKeyDown = mPendingKeyDown; - final Key currentKey = mCurrentKey; - releaseCurrentKey(); - if (key == null) { - return false; - } - if (key == currentKey && pendingKeyDown != null) { - pendingKeyDown.run(); - // Trigger key-release event a little later so that a user can see visual feedback. - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - key.onReleased(); - invalidateKey(key); - mListener.onReleaseKey(key); - } - }, KEY_RELEASE_DELAY_TIME); - } else { - key.onReleased(); - invalidateKey(key); - mListener.onReleaseKey(key); - } - return true; - } - - @Override - public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX, - final float distanceY) { - releaseCurrentKey(); - return false; - } - - @Override - public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, - final float velocityY) { - releaseCurrentKey(); - return false; - } - - @Override - public void onLongPress(final MotionEvent e) { - // Long press detection of {@link #mGestureDetector} is disabled and not used. - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/EmojiPalettesAdapter.java b/java/src/com/android/inputmethod/keyboard/internal/EmojiPalettesAdapter.java deleted file mode 100644 index a44d13407..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/EmojiPalettesAdapter.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2014 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.internal; - -import android.support.v4.view.PagerAdapter; -import android.util.Log; -import android.util.SparseArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardView; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.CollectionUtils; - -// TODO: Move this class to com.android.inputmethod.emoji package. -public final class EmojiPalettesAdapter extends PagerAdapter { - private static final String TAG = EmojiPalettesAdapter.class.getSimpleName(); - private static final boolean DEBUG_PAGER = false; - - private final EmojiPageKeyboardView.OnKeyEventListener 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 EmojiPageKeyboardView.OnKeyEventListener listener) { - mEmojiCategory = emojiCategory; - mListener = listener; - mRecentsKeyboard = mEmojiCategory.getKeyboard(EmojiCategory.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(); - } - } - - public void onPageScrolled() { - // Make sure the delayed key-down event (highlight effect and haptic feedback) will be - // canceled. - final EmojiPageKeyboardView currentKeyboardView = - mActiveKeyboardViews.get(mActivePosition); - if (currentKeyboardView != null) { - currentKeyboardView.releaseCurrentKey(); - } - } - - @Override - public int getCount() { - return mEmojiCategory.getTotalPageCountOfAllCategories(); - } - - @Override - public void setPrimaryItem(final ViewGroup container, final int position, - final Object object) { - if (mActivePosition == position) { - return; - } - final EmojiPageKeyboardView 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 EmojiPageKeyboardView 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 EmojiPageKeyboardView keyboardView = (EmojiPageKeyboardView)inflater.inflate( - R.layout.emoji_keyboard_page, container, false /* attachToRoot */); - keyboardView.setKeyboard(keyboard); - keyboardView.setOnKeyEventListener(mListener); - container.addView(keyboardView); - mActiveKeyboardViews.put(position, keyboardView); - return keyboardView; - } - - @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 EmojiPageKeyboardView 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); - } - } -} -- cgit v1.2.3-83-g751a