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 android.content.Context; 20 import android.os.Handler; 21 import android.util.AttributeSet; 22 import android.view.GestureDetector; 23 import android.view.MotionEvent; 24 import android.view.accessibility.AccessibilityEvent; 25 26 import com.android.inputmethod.accessibility.AccessibilityUtils; 27 import com.android.inputmethod.accessibility.KeyboardAccessibilityDelegate; 28 import com.android.inputmethod.keyboard.Key; 29 import com.android.inputmethod.keyboard.KeyDetector; 30 import com.android.inputmethod.keyboard.Keyboard; 31 import com.android.inputmethod.keyboard.KeyboardView; 32 import com.android.inputmethod.latin.R; 33 34 /** 35 * This is an extended {@link KeyboardView} class that hosts an emoji page keyboard. 36 * Multi-touch unsupported. No gesture support. 37 */ 38 // TODO: Implement key popup preview. 39 final class EmojiPageKeyboardView extends KeyboardView implements 40 GestureDetector.OnGestureListener { 41 private static final long KEY_PRESS_DELAY_TIME = 250; // msec 42 private static final long KEY_RELEASE_DELAY_TIME = 30; // msec 43 44 public interface OnKeyEventListener { onPressKey(Key key)45 public void onPressKey(Key key); onReleaseKey(Key key)46 public void onReleaseKey(Key key); 47 } 48 49 private static final OnKeyEventListener EMPTY_LISTENER = new OnKeyEventListener() { 50 @Override 51 public void onPressKey(final Key key) {} 52 @Override 53 public void onReleaseKey(final Key key) {} 54 }; 55 56 private OnKeyEventListener mListener = EMPTY_LISTENER; 57 private final KeyDetector mKeyDetector = new KeyDetector(); 58 private final GestureDetector mGestureDetector; 59 private KeyboardAccessibilityDelegate<EmojiPageKeyboardView> mAccessibilityDelegate; 60 EmojiPageKeyboardView(final Context context, final AttributeSet attrs)61 public EmojiPageKeyboardView(final Context context, final AttributeSet attrs) { 62 this(context, attrs, R.attr.keyboardViewStyle); 63 } 64 EmojiPageKeyboardView(final Context context, final AttributeSet attrs, final int defStyle)65 public EmojiPageKeyboardView(final Context context, final AttributeSet attrs, 66 final int defStyle) { 67 super(context, attrs, defStyle); 68 mGestureDetector = new GestureDetector(context, this); 69 mGestureDetector.setIsLongpressEnabled(false /* isLongpressEnabled */); 70 mHandler = new Handler(); 71 } 72 setOnKeyEventListener(final OnKeyEventListener listener)73 public void setOnKeyEventListener(final OnKeyEventListener listener) { 74 mListener = listener; 75 } 76 77 /** 78 * {@inheritDoc} 79 */ 80 @Override setKeyboard(final Keyboard keyboard)81 public void setKeyboard(final Keyboard keyboard) { 82 super.setKeyboard(keyboard); 83 mKeyDetector.setKeyboard(keyboard, 0 /* correctionX */, 0 /* correctionY */); 84 if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) { 85 if (mAccessibilityDelegate == null) { 86 mAccessibilityDelegate = new KeyboardAccessibilityDelegate<>(this, mKeyDetector); 87 } 88 mAccessibilityDelegate.setKeyboard(keyboard); 89 } else { 90 mAccessibilityDelegate = null; 91 } 92 } 93 94 @Override dispatchPopulateAccessibilityEvent(final AccessibilityEvent event)95 public boolean dispatchPopulateAccessibilityEvent(final AccessibilityEvent event) { 96 // Don't populate accessibility event with all Emoji keys. 97 return true; 98 } 99 100 /** 101 * {@inheritDoc} 102 */ 103 @Override onHoverEvent(final MotionEvent event)104 public boolean onHoverEvent(final MotionEvent event) { 105 final KeyboardAccessibilityDelegate<EmojiPageKeyboardView> accessibilityDelegate = 106 mAccessibilityDelegate; 107 if (accessibilityDelegate != null 108 && AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { 109 return accessibilityDelegate.onHoverEvent(event); 110 } 111 return super.onHoverEvent(event); 112 } 113 114 /** 115 * {@inheritDoc} 116 */ 117 @Override onTouchEvent(final MotionEvent e)118 public boolean onTouchEvent(final MotionEvent e) { 119 if (mGestureDetector.onTouchEvent(e)) { 120 return true; 121 } 122 final Key key = getKey(e); 123 if (key != null && key != mCurrentKey) { 124 releaseCurrentKey(false /* withKeyRegistering */); 125 } 126 return true; 127 } 128 129 // {@link GestureEnabler#OnGestureListener} methods. 130 private Key mCurrentKey; 131 private Runnable mPendingKeyDown; 132 private final Handler mHandler; 133 getKey(final MotionEvent e)134 private Key getKey(final MotionEvent e) { 135 final int index = e.getActionIndex(); 136 final int x = (int)e.getX(index); 137 final int y = (int)e.getY(index); 138 return mKeyDetector.detectHitKey(x, y); 139 } 140 callListenerOnReleaseKey(final Key releasedKey, final boolean withKeyRegistering)141 void callListenerOnReleaseKey(final Key releasedKey, final boolean withKeyRegistering) { 142 releasedKey.onReleased(); 143 invalidateKey(releasedKey); 144 if (withKeyRegistering) { 145 mListener.onReleaseKey(releasedKey); 146 } 147 } 148 callListenerOnPressKey(final Key pressedKey)149 void callListenerOnPressKey(final Key pressedKey) { 150 mPendingKeyDown = null; 151 pressedKey.onPressed(); 152 invalidateKey(pressedKey); 153 mListener.onPressKey(pressedKey); 154 } 155 releaseCurrentKey(final boolean withKeyRegistering)156 public void releaseCurrentKey(final boolean withKeyRegistering) { 157 mHandler.removeCallbacks(mPendingKeyDown); 158 mPendingKeyDown = null; 159 final Key currentKey = mCurrentKey; 160 if (currentKey == null) { 161 return; 162 } 163 callListenerOnReleaseKey(currentKey, withKeyRegistering); 164 mCurrentKey = null; 165 } 166 167 @Override onDown(final MotionEvent e)168 public boolean onDown(final MotionEvent e) { 169 final Key key = getKey(e); 170 releaseCurrentKey(false /* withKeyRegistering */); 171 mCurrentKey = key; 172 if (key == null) { 173 return false; 174 } 175 // Do not trigger key-down effect right now in case this is actually a fling action. 176 mPendingKeyDown = new Runnable() { 177 @Override 178 public void run() { 179 callListenerOnPressKey(key); 180 } 181 }; 182 mHandler.postDelayed(mPendingKeyDown, KEY_PRESS_DELAY_TIME); 183 return false; 184 } 185 186 @Override onShowPress(final MotionEvent e)187 public void onShowPress(final MotionEvent e) { 188 // User feedback is done at {@link #onDown(MotionEvent)}. 189 } 190 191 @Override onSingleTapUp(final MotionEvent e)192 public boolean onSingleTapUp(final MotionEvent e) { 193 final Key key = getKey(e); 194 final Runnable pendingKeyDown = mPendingKeyDown; 195 final Key currentKey = mCurrentKey; 196 releaseCurrentKey(false /* withKeyRegistering */); 197 if (key == null) { 198 return false; 199 } 200 if (key == currentKey && pendingKeyDown != null) { 201 pendingKeyDown.run(); 202 // Trigger key-release event a little later so that a user can see visual feedback. 203 mHandler.postDelayed(new Runnable() { 204 @Override 205 public void run() { 206 callListenerOnReleaseKey(key, true /* withRegistering */); 207 } 208 }, KEY_RELEASE_DELAY_TIME); 209 } else { 210 callListenerOnReleaseKey(key, true /* withRegistering */); 211 } 212 return true; 213 } 214 215 @Override onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX, final float distanceY)216 public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX, 217 final float distanceY) { 218 releaseCurrentKey(false /* withKeyRegistering */); 219 return false; 220 } 221 222 @Override onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY)223 public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, 224 final float velocityY) { 225 releaseCurrentKey(false /* withKeyRegistering */); 226 return false; 227 } 228 229 @Override onLongPress(final MotionEvent e)230 public void onLongPress(final MotionEvent e) { 231 // Long press detection of {@link #mGestureDetector} is disabled and not used. 232 } 233 } 234