1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.inputmethod.keyboard.emoji; 18 19 import static com.android.inputmethod.latin.Constants.NOT_A_COORDINATE; 20 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.content.res.TypedArray; 24 import android.graphics.Color; 25 import android.os.CountDownTimer; 26 import android.preference.PreferenceManager; 27 import android.support.v4.view.ViewPager; 28 import android.util.AttributeSet; 29 import android.util.Pair; 30 import android.util.TypedValue; 31 import android.view.LayoutInflater; 32 import android.view.MotionEvent; 33 import android.view.View; 34 import android.widget.ImageButton; 35 import android.widget.ImageView; 36 import android.widget.LinearLayout; 37 import android.widget.TabHost; 38 import android.widget.TabHost.OnTabChangeListener; 39 import android.widget.TabWidget; 40 import android.widget.TextView; 41 42 import com.android.inputmethod.keyboard.Key; 43 import com.android.inputmethod.keyboard.KeyboardActionListener; 44 import com.android.inputmethod.keyboard.KeyboardLayoutSet; 45 import com.android.inputmethod.keyboard.KeyboardView; 46 import com.android.inputmethod.keyboard.internal.KeyDrawParams; 47 import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; 48 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; 49 import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; 50 import com.android.inputmethod.latin.Constants; 51 import com.android.inputmethod.latin.R; 52 import com.android.inputmethod.latin.SubtypeSwitcher; 53 import com.android.inputmethod.latin.utils.ResourceUtils; 54 55 import java.util.concurrent.TimeUnit; 56 57 /** 58 * View class to implement Emoji palettes. 59 * The Emoji keyboard consists of group of views layout/emoji_palettes_view. 60 * <ol> 61 * <li> Emoji category tabs. 62 * <li> Delete button. 63 * <li> Emoji keyboard pages that can be scrolled by swiping horizontally or by selecting a tab. 64 * <li> Back to main keyboard button and enter button. 65 * </ol> 66 * Because of the above reasons, this class doesn't extend {@link KeyboardView}. 67 */ 68 public final class EmojiPalettesView extends LinearLayout implements OnTabChangeListener, 69 ViewPager.OnPageChangeListener, View.OnClickListener, View.OnTouchListener, 70 EmojiPageKeyboardView.OnKeyEventListener { 71 private final int mFunctionalKeyBackgroundId; 72 private final int mSpacebarBackgroundId; 73 private final boolean mCategoryIndicatorEnabled; 74 private final int mCategoryIndicatorDrawableResId; 75 private final int mCategoryIndicatorBackgroundResId; 76 private final int mCategoryPageIndicatorColor; 77 private final int mCategoryPageIndicatorBackground; 78 private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener; 79 private EmojiPalettesAdapter mEmojiPalettesAdapter; 80 private final EmojiLayoutParams mEmojiLayoutParams; 81 82 private ImageButton mDeleteKey; 83 private TextView mAlphabetKeyLeft; 84 private TextView mAlphabetKeyRight; 85 private View mSpacebar; 86 // TODO: Remove this workaround. 87 private View mSpacebarIcon; 88 private TabHost mTabHost; 89 private ViewPager mEmojiPager; 90 private int mCurrentPagerPosition = 0; 91 private EmojiCategoryPageIndicatorView mEmojiCategoryPageIndicatorView; 92 93 private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER; 94 95 private final EmojiCategory mEmojiCategory; 96 97 public EmojiPalettesView(final Context context, final AttributeSet attrs) { 98 this(context, attrs, R.attr.emojiPalettesViewStyle); 99 } 100 101 public EmojiPalettesView(final Context context, final AttributeSet attrs, final int defStyle) { 102 super(context, attrs, defStyle); 103 final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs, 104 R.styleable.KeyboardView, defStyle, R.style.KeyboardView); 105 final int keyBackgroundId = keyboardViewAttr.getResourceId( 106 R.styleable.KeyboardView_keyBackground, 0); 107 mFunctionalKeyBackgroundId = keyboardViewAttr.getResourceId( 108 R.styleable.KeyboardView_functionalKeyBackground, keyBackgroundId); 109 mSpacebarBackgroundId = keyboardViewAttr.getResourceId( 110 R.styleable.KeyboardView_spacebarBackground, keyBackgroundId); 111 keyboardViewAttr.recycle(); 112 final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( 113 context, null /* editorInfo */); 114 final Resources res = context.getResources(); 115 mEmojiLayoutParams = new EmojiLayoutParams(res); 116 builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype()); 117 builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res), 118 mEmojiLayoutParams.mEmojiKeyboardHeight); 119 final KeyboardLayoutSet layoutSet = builder.build(); 120 final TypedArray emojiPalettesViewAttr = context.obtainStyledAttributes(attrs, 121 R.styleable.EmojiPalettesView, defStyle, R.style.EmojiPalettesView); 122 mEmojiCategory = new EmojiCategory(PreferenceManager.getDefaultSharedPreferences(context), 123 res, layoutSet, emojiPalettesViewAttr); 124 mCategoryIndicatorEnabled = emojiPalettesViewAttr.getBoolean( 125 R.styleable.EmojiPalettesView_categoryIndicatorEnabled, false); 126 mCategoryIndicatorDrawableResId = emojiPalettesViewAttr.getResourceId( 127 R.styleable.EmojiPalettesView_categoryIndicatorDrawable, 0); 128 mCategoryIndicatorBackgroundResId = emojiPalettesViewAttr.getResourceId( 129 R.styleable.EmojiPalettesView_categoryIndicatorBackground, 0); 130 mCategoryPageIndicatorColor = emojiPalettesViewAttr.getColor( 131 R.styleable.EmojiPalettesView_categoryPageIndicatorColor, 0); 132 mCategoryPageIndicatorBackground = emojiPalettesViewAttr.getColor( 133 R.styleable.EmojiPalettesView_categoryPageIndicatorBackground, 0); 134 emojiPalettesViewAttr.recycle(); 135 mDeleteKeyOnTouchListener = new DeleteKeyOnTouchListener(context); 136 } 137 138 @Override 139 protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { 140 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 141 final Resources res = getContext().getResources(); 142 // The main keyboard expands to the entire this {@link KeyboardView}. 143 final int width = ResourceUtils.getDefaultKeyboardWidth(res) 144 + getPaddingLeft() + getPaddingRight(); 145 final int height = ResourceUtils.getDefaultKeyboardHeight(res) 146 + res.getDimensionPixelSize(R.dimen.config_suggestions_strip_height) 147 + getPaddingTop() + getPaddingBottom(); 148 setMeasuredDimension(width, height); 149 } 150 151 private void addTab(final TabHost host, final int categoryId) { 152 final String tabId = mEmojiCategory.getCategoryName(categoryId, 0 /* categoryPageId */); 153 final TabHost.TabSpec tspec = host.newTabSpec(tabId); 154 tspec.setContent(R.id.emoji_keyboard_dummy); 155 final ImageView iconView = (ImageView)LayoutInflater.from(getContext()).inflate( 156 R.layout.emoji_keyboard_tab_icon, null); 157 iconView.setImageResource(mEmojiCategory.getCategoryTabIcon(categoryId)); 158 iconView.setContentDescription(mEmojiCategory.getAccessibilityDescription(categoryId)); 159 tspec.setIndicator(iconView); 160 host.addTab(tspec); 161 } 162 163 @Override 164 protected void onFinishInflate() { 165 mTabHost = (TabHost)findViewById(R.id.emoji_category_tabhost); 166 mTabHost.setup(); 167 for (final EmojiCategory.CategoryProperties properties 168 : mEmojiCategory.getShownCategories()) { 169 addTab(mTabHost, properties.mCategoryId); 170 } 171 mTabHost.setOnTabChangedListener(this); 172 final TabWidget tabWidget = mTabHost.getTabWidget(); 173 tabWidget.setStripEnabled(mCategoryIndicatorEnabled); 174 if (mCategoryIndicatorEnabled) { 175 // On TabWidget's strip, what looks like an indicator is actually a background. 176 // And what looks like a background are actually left and right drawables. 177 tabWidget.setBackgroundResource(mCategoryIndicatorDrawableResId); 178 tabWidget.setLeftStripDrawable(mCategoryIndicatorBackgroundResId); 179 tabWidget.setRightStripDrawable(mCategoryIndicatorBackgroundResId); 180 } 181 182 mEmojiPalettesAdapter = new EmojiPalettesAdapter(mEmojiCategory, this); 183 184 mEmojiPager = (ViewPager)findViewById(R.id.emoji_keyboard_pager); 185 mEmojiPager.setAdapter(mEmojiPalettesAdapter); 186 mEmojiPager.setOnPageChangeListener(this); 187 mEmojiPager.setOffscreenPageLimit(0); 188 mEmojiPager.setPersistentDrawingCache(PERSISTENT_NO_CACHE); 189 mEmojiLayoutParams.setPagerProperties(mEmojiPager); 190 191 mEmojiCategoryPageIndicatorView = 192 (EmojiCategoryPageIndicatorView)findViewById(R.id.emoji_category_page_id_view); 193 mEmojiCategoryPageIndicatorView.setColors( 194 mCategoryPageIndicatorColor, mCategoryPageIndicatorBackground); 195 mEmojiLayoutParams.setCategoryPageIdViewProperties(mEmojiCategoryPageIndicatorView); 196 197 setCurrentCategoryId(mEmojiCategory.getCurrentCategoryId(), true /* force */); 198 199 final LinearLayout actionBar = (LinearLayout)findViewById(R.id.emoji_action_bar); 200 mEmojiLayoutParams.setActionBarProperties(actionBar); 201 202 // deleteKey depends only on OnTouchListener. 203 mDeleteKey = (ImageButton)findViewById(R.id.emoji_keyboard_delete); 204 mDeleteKey.setBackgroundResource(mFunctionalKeyBackgroundId); 205 mDeleteKey.setTag(Constants.CODE_DELETE); 206 mDeleteKey.setOnTouchListener(mDeleteKeyOnTouchListener); 207 208 // {@link #mAlphabetKeyLeft}, {@link #mAlphabetKeyRight, and spaceKey depend on 209 // {@link View.OnClickListener} as well as {@link View.OnTouchListener}. 210 // {@link View.OnTouchListener} is used as the trigger of key-press, while 211 // {@link View.OnClickListener} is used as the trigger of key-release which does not occur 212 // if the event is canceled by moving off the finger from the view. 213 // The text on alphabet keys are set at 214 // {@link #startEmojiPalettes(String,int,float,Typeface)}. 215 mAlphabetKeyLeft = (TextView)findViewById(R.id.emoji_keyboard_alphabet_left); 216 mAlphabetKeyLeft.setBackgroundResource(mFunctionalKeyBackgroundId); 217 mAlphabetKeyLeft.setTag(Constants.CODE_ALPHA_FROM_EMOJI); 218 mAlphabetKeyLeft.setOnTouchListener(this); 219 mAlphabetKeyLeft.setOnClickListener(this); 220 mAlphabetKeyRight = (TextView)findViewById(R.id.emoji_keyboard_alphabet_right); 221 mAlphabetKeyRight.setBackgroundResource(mFunctionalKeyBackgroundId); 222 mAlphabetKeyRight.setTag(Constants.CODE_ALPHA_FROM_EMOJI); 223 mAlphabetKeyRight.setOnTouchListener(this); 224 mAlphabetKeyRight.setOnClickListener(this); 225 mSpacebar = findViewById(R.id.emoji_keyboard_space); 226 mSpacebar.setBackgroundResource(mSpacebarBackgroundId); 227 mSpacebar.setTag(Constants.CODE_SPACE); 228 mSpacebar.setOnTouchListener(this); 229 mSpacebar.setOnClickListener(this); 230 mEmojiLayoutParams.setKeyProperties(mSpacebar); 231 mSpacebarIcon = findViewById(R.id.emoji_keyboard_space_icon); 232 } 233 234 @Override 235 public boolean dispatchTouchEvent(final MotionEvent ev) { 236 // Add here to the stack trace to nail down the {@link IllegalArgumentException} exception 237 // in MotionEvent that sporadically happens. 238 // TODO: Remove this override method once the issue has been addressed. 239 return super.dispatchTouchEvent(ev); 240 } 241 242 @Override 243 public void onTabChanged(final String tabId) { 244 AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback( 245 Constants.CODE_UNSPECIFIED, this); 246 final int categoryId = mEmojiCategory.getCategoryId(tabId); 247 setCurrentCategoryId(categoryId, false /* force */); 248 updateEmojiCategoryPageIdView(); 249 } 250 251 @Override 252 public void onPageSelected(final int position) { 253 final Pair<Integer, Integer> newPos = 254 mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position); 255 setCurrentCategoryId(newPos.first /* categoryId */, false /* force */); 256 mEmojiCategory.setCurrentCategoryPageId(newPos.second /* categoryPageId */); 257 updateEmojiCategoryPageIdView(); 258 mCurrentPagerPosition = position; 259 } 260 261 @Override 262 public void onPageScrollStateChanged(final int state) { 263 // Ignore this message. Only want the actual page selected. 264 } 265 266 @Override 267 public void onPageScrolled(final int position, final float positionOffset, 268 final int positionOffsetPixels) { 269 mEmojiPalettesAdapter.onPageScrolled(); 270 final Pair<Integer, Integer> newPos = 271 mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position); 272 final int newCategoryId = newPos.first; 273 final int newCategorySize = mEmojiCategory.getCategoryPageSize(newCategoryId); 274 final int currentCategoryId = mEmojiCategory.getCurrentCategoryId(); 275 final int currentCategoryPageId = mEmojiCategory.getCurrentCategoryPageId(); 276 final int currentCategorySize = mEmojiCategory.getCurrentCategoryPageSize(); 277 if (newCategoryId == currentCategoryId) { 278 mEmojiCategoryPageIndicatorView.setCategoryPageId( 279 newCategorySize, newPos.second, positionOffset); 280 } else if (newCategoryId > currentCategoryId) { 281 mEmojiCategoryPageIndicatorView.setCategoryPageId( 282 currentCategorySize, currentCategoryPageId, positionOffset); 283 } else if (newCategoryId < currentCategoryId) { 284 mEmojiCategoryPageIndicatorView.setCategoryPageId( 285 currentCategorySize, currentCategoryPageId, positionOffset - 1); 286 } 287 } 288 289 /** 290 * Called from {@link EmojiPageKeyboardView} through {@link android.view.View.OnTouchListener} 291 * interface to handle touch events from View-based elements such as the space bar. 292 * Note that this method is used only for observing {@link MotionEvent#ACTION_DOWN} to trigger 293 * {@link KeyboardActionListener#onPressKey}. {@link KeyboardActionListener#onReleaseKey} will 294 * be covered by {@link #onClick} as long as the event is not canceled. 295 */ 296 @Override 297 public boolean onTouch(final View v, final MotionEvent event) { 298 if (event.getActionMasked() != MotionEvent.ACTION_DOWN) { 299 return false; 300 } 301 final Object tag = v.getTag(); 302 if (!(tag instanceof Integer)) { 303 return false; 304 } 305 final int code = (Integer) tag; 306 mKeyboardActionListener.onPressKey( 307 code, 0 /* repeatCount */, true /* isSinglePointer */); 308 // It's important to return false here. Otherwise, {@link #onClick} and touch-down visual 309 // feedback stop working. 310 return false; 311 } 312 313 /** 314 * Called from {@link EmojiPageKeyboardView} through {@link android.view.View.OnClickListener} 315 * interface to handle non-canceled touch-up events from View-based elements such as the space 316 * bar. 317 */ 318 @Override 319 public void onClick(View v) { 320 final Object tag = v.getTag(); 321 if (!(tag instanceof Integer)) { 322 return; 323 } 324 final int code = (Integer) tag; 325 mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE, 326 false /* isKeyRepeat */); 327 mKeyboardActionListener.onReleaseKey(code, false /* withSliding */); 328 } 329 330 /** 331 * Called from {@link EmojiPageKeyboardView} through 332 * {@link com.android.inputmethod.keyboard.emoji.EmojiPageKeyboardView.OnKeyEventListener} 333 * interface to handle touch events from non-View-based elements such as Emoji buttons. 334 */ 335 @Override 336 public void onPressKey(final Key key) { 337 final int code = key.getCode(); 338 mKeyboardActionListener.onPressKey(code, 0 /* repeatCount */, true /* isSinglePointer */); 339 } 340 341 /** 342 * Called from {@link EmojiPageKeyboardView} through 343 * {@link com.android.inputmethod.keyboard.emoji.EmojiPageKeyboardView.OnKeyEventListener} 344 * interface to handle touch events from non-View-based elements such as Emoji buttons. 345 */ 346 @Override 347 public void onReleaseKey(final Key key) { 348 mEmojiPalettesAdapter.addRecentKey(key); 349 mEmojiCategory.saveLastTypedCategoryPage(); 350 final int code = key.getCode(); 351 if (code == Constants.CODE_OUTPUT_TEXT) { 352 mKeyboardActionListener.onTextInput(key.getOutputText()); 353 } else { 354 mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE, 355 false /* isKeyRepeat */); 356 } 357 mKeyboardActionListener.onReleaseKey(code, false /* withSliding */); 358 } 359 360 public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { 361 if (!enabled) return; 362 // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off? 363 setLayerType(LAYER_TYPE_HARDWARE, null); 364 } 365 366 private static void setupAlphabetKey(final TextView alphabetKey, final String label, 367 final KeyDrawParams params) { 368 alphabetKey.setText(label); 369 alphabetKey.setTextColor(params.mFunctionalTextColor); 370 alphabetKey.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mLabelSize); 371 alphabetKey.setTypeface(params.mTypeface); 372 } 373 374 public void startEmojiPalettes(final String switchToAlphaLabel, 375 final KeyVisualAttributes keyVisualAttr, final KeyboardIconsSet iconSet) { 376 final int deleteIconResId = iconSet.getIconResourceId(KeyboardIconsSet.NAME_DELETE_KEY); 377 if (deleteIconResId != 0) { 378 mDeleteKey.setImageResource(deleteIconResId); 379 } 380 final int spacebarResId = iconSet.getIconResourceId(KeyboardIconsSet.NAME_SPACE_KEY); 381 if (spacebarResId != 0) { 382 // TODO: Remove this workaround to place the spacebar icon. 383 mSpacebarIcon.setBackgroundResource(spacebarResId); 384 } 385 final KeyDrawParams params = new KeyDrawParams(); 386 params.updateParams(mEmojiLayoutParams.getActionBarHeight(), keyVisualAttr); 387 setupAlphabetKey(mAlphabetKeyLeft, switchToAlphaLabel, params); 388 setupAlphabetKey(mAlphabetKeyRight, switchToAlphaLabel, params); 389 mEmojiPager.setAdapter(mEmojiPalettesAdapter); 390 mEmojiPager.setCurrentItem(mCurrentPagerPosition); 391 } 392 393 public void stopEmojiPalettes() { 394 mEmojiPalettesAdapter.releaseCurrentKey(true /* withKeyRegistering */); 395 mEmojiPalettesAdapter.flushPendingRecentKeys(); 396 mEmojiPager.setAdapter(null); 397 } 398 399 public void setKeyboardActionListener(final KeyboardActionListener listener) { 400 mKeyboardActionListener = listener; 401 mDeleteKeyOnTouchListener.setKeyboardActionListener(mKeyboardActionListener); 402 } 403 404 private void updateEmojiCategoryPageIdView() { 405 if (mEmojiCategoryPageIndicatorView == null) { 406 return; 407 } 408 mEmojiCategoryPageIndicatorView.setCategoryPageId( 409 mEmojiCategory.getCurrentCategoryPageSize(), 410 mEmojiCategory.getCurrentCategoryPageId(), 0.0f /* offset */); 411 } 412 413 private void setCurrentCategoryId(final int categoryId, final boolean force) { 414 final int oldCategoryId = mEmojiCategory.getCurrentCategoryId(); 415 if (oldCategoryId == categoryId && !force) { 416 return; 417 } 418 419 if (oldCategoryId == EmojiCategory.ID_RECENTS) { 420 // Needs to save pending updates for recent keys when we get out of the recents 421 // category because we don't want to move the recent emojis around while the user 422 // is in the recents category. 423 mEmojiPalettesAdapter.flushPendingRecentKeys(); 424 } 425 426 mEmojiCategory.setCurrentCategoryId(categoryId); 427 final int newTabId = mEmojiCategory.getTabIdFromCategoryId(categoryId); 428 final int newCategoryPageId = mEmojiCategory.getPageIdFromCategoryId(categoryId); 429 if (force || mEmojiCategory.getCategoryIdAndPageIdFromPagePosition( 430 mEmojiPager.getCurrentItem()).first != categoryId) { 431 mEmojiPager.setCurrentItem(newCategoryPageId, false /* smoothScroll */); 432 } 433 if (force || mTabHost.getCurrentTab() != newTabId) { 434 mTabHost.setCurrentTab(newTabId); 435 } 436 } 437 438 private static class DeleteKeyOnTouchListener implements OnTouchListener { 439 static final long MAX_REPEAT_COUNT_TIME = TimeUnit.SECONDS.toMillis(30); 440 final long mKeyRepeatStartTimeout; 441 final long mKeyRepeatInterval; 442 443 public DeleteKeyOnTouchListener(Context context) { 444 final Resources res = context.getResources(); 445 mKeyRepeatStartTimeout = res.getInteger(R.integer.config_key_repeat_start_timeout); 446 mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval); 447 mTimer = new CountDownTimer(MAX_REPEAT_COUNT_TIME, mKeyRepeatInterval) { 448 @Override 449 public void onTick(long millisUntilFinished) { 450 final long elapsed = MAX_REPEAT_COUNT_TIME - millisUntilFinished; 451 if (elapsed < mKeyRepeatStartTimeout) { 452 return; 453 } 454 onKeyRepeat(); 455 } 456 @Override 457 public void onFinish() { 458 onKeyRepeat(); 459 } 460 }; 461 } 462 463 /** Key-repeat state. */ 464 private static final int KEY_REPEAT_STATE_INITIALIZED = 0; 465 // The key is touched but auto key-repeat is not started yet. 466 private static final int KEY_REPEAT_STATE_KEY_DOWN = 1; 467 // At least one key-repeat event has already been triggered and the key is not released. 468 private static final int KEY_REPEAT_STATE_KEY_REPEAT = 2; 469 470 private KeyboardActionListener mKeyboardActionListener = 471 KeyboardActionListener.EMPTY_LISTENER; 472 473 // TODO: Do the same things done in PointerTracker 474 private final CountDownTimer mTimer; 475 private int mState = KEY_REPEAT_STATE_INITIALIZED; 476 private int mRepeatCount = 0; 477 478 public void setKeyboardActionListener(final KeyboardActionListener listener) { 479 mKeyboardActionListener = listener; 480 } 481 482 @Override 483 public boolean onTouch(final View v, final MotionEvent event) { 484 switch (event.getActionMasked()) { 485 case MotionEvent.ACTION_DOWN: 486 onTouchDown(v); 487 return true; 488 case MotionEvent.ACTION_MOVE: 489 final float x = event.getX(); 490 final float y = event.getY(); 491 if (x < 0.0f || v.getWidth() < x || y < 0.0f || v.getHeight() < y) { 492 // Stop generating key events once the finger moves away from the view area. 493 onTouchCanceled(v); 494 } 495 return true; 496 case MotionEvent.ACTION_CANCEL: 497 case MotionEvent.ACTION_UP: 498 onTouchUp(v); 499 return true; 500 } 501 return false; 502 } 503 504 private void handleKeyDown() { 505 mKeyboardActionListener.onPressKey( 506 Constants.CODE_DELETE, mRepeatCount, true /* isSinglePointer */); 507 } 508 509 private void handleKeyUp() { 510 mKeyboardActionListener.onCodeInput(Constants.CODE_DELETE, 511 NOT_A_COORDINATE, NOT_A_COORDINATE, false /* isKeyRepeat */); 512 mKeyboardActionListener.onReleaseKey( 513 Constants.CODE_DELETE, false /* withSliding */); 514 ++mRepeatCount; 515 } 516 517 private void onTouchDown(final View v) { 518 mTimer.cancel(); 519 mRepeatCount = 0; 520 handleKeyDown(); 521 v.setPressed(true /* pressed */); 522 mState = KEY_REPEAT_STATE_KEY_DOWN; 523 mTimer.start(); 524 } 525 526 private void onTouchUp(final View v) { 527 mTimer.cancel(); 528 if (mState == KEY_REPEAT_STATE_KEY_DOWN) { 529 handleKeyUp(); 530 } 531 v.setPressed(false /* pressed */); 532 mState = KEY_REPEAT_STATE_INITIALIZED; 533 } 534 535 private void onTouchCanceled(final View v) { 536 mTimer.cancel(); 537 v.setBackgroundColor(Color.TRANSPARENT); 538 mState = KEY_REPEAT_STATE_INITIALIZED; 539 } 540 541 // Called by {@link #mTimer} in the UI thread as an auto key-repeat signal. 542 void onKeyRepeat() { 543 switch (mState) { 544 case KEY_REPEAT_STATE_INITIALIZED: 545 // Basically this should not happen. 546 break; 547 case KEY_REPEAT_STATE_KEY_DOWN: 548 // Do not call {@link #handleKeyDown} here because it has already been called 549 // in {@link #onTouchDown}. 550 handleKeyUp(); 551 mState = KEY_REPEAT_STATE_KEY_REPEAT; 552 break; 553 case KEY_REPEAT_STATE_KEY_REPEAT: 554 handleKeyDown(); 555 handleKeyUp(); 556 break; 557 } 558 } 559 } 560 } 561