EmojiPageKeyboardView.java revision adba09b54ed1b30bf9b24d632165229a0752b144
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 17package com.android.inputmethod.keyboard.emoji; 18 19import android.content.Context; 20import android.os.Handler; 21import android.util.AttributeSet; 22import android.view.GestureDetector; 23import android.view.MotionEvent; 24import android.view.accessibility.AccessibilityEvent; 25 26import com.android.inputmethod.accessibility.AccessibilityUtils; 27import com.android.inputmethod.accessibility.KeyboardAccessibilityDelegate; 28import com.android.inputmethod.keyboard.Key; 29import com.android.inputmethod.keyboard.KeyDetector; 30import com.android.inputmethod.keyboard.Keyboard; 31import com.android.inputmethod.keyboard.KeyboardView; 32import 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. 39final 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 { 45 public void onPressKey(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 61 public EmojiPageKeyboardView(final Context context, final AttributeSet attrs) { 62 this(context, attrs, R.attr.keyboardViewStyle); 63 } 64 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 73 public void setOnKeyEventListener(final OnKeyEventListener listener) { 74 mListener = listener; 75 } 76 77 /** 78 * {@inheritDoc} 79 */ 80 @Override 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 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 104 public boolean onHoverEvent(final MotionEvent event) { 105 final KeyboardAccessibilityDelegate<EmojiPageKeyboardView> accessibilityDelegate = 106 mAccessibilityDelegate; 107 if (accessibilityDelegate != null) { 108 return accessibilityDelegate.onHoverEvent(event); 109 } 110 return super.onHoverEvent(event); 111 } 112 113 /** 114 * {@inheritDoc} 115 */ 116 @Override 117 public boolean onTouchEvent(final MotionEvent e) { 118 if (mGestureDetector.onTouchEvent(e)) { 119 return true; 120 } 121 final Key key = getKey(e); 122 if (key != null && key != mCurrentKey) { 123 releaseCurrentKey(false /* withKeyRegistering */); 124 } 125 return true; 126 } 127 128 // {@link GestureEnabler#OnGestureListener} methods. 129 private Key mCurrentKey; 130 private Runnable mPendingKeyDown; 131 private final Handler mHandler; 132 133 private Key getKey(final MotionEvent e) { 134 final int index = e.getActionIndex(); 135 final int x = (int)e.getX(index); 136 final int y = (int)e.getY(index); 137 return mKeyDetector.detectHitKey(x, y); 138 } 139 140 public void releaseCurrentKey(final boolean withKeyRegistering) { 141 mHandler.removeCallbacks(mPendingKeyDown); 142 mPendingKeyDown = null; 143 final Key currentKey = mCurrentKey; 144 if (currentKey == null) { 145 return; 146 } 147 currentKey.onReleased(); 148 invalidateKey(currentKey); 149 if (withKeyRegistering) { 150 mListener.onReleaseKey(currentKey); 151 } 152 mCurrentKey = null; 153 } 154 155 @Override 156 public boolean onDown(final MotionEvent e) { 157 final Key key = getKey(e); 158 releaseCurrentKey(false /* withKeyRegistering */); 159 mCurrentKey = key; 160 if (key == null) { 161 return false; 162 } 163 // Do not trigger key-down effect right now in case this is actually a fling action. 164 mPendingKeyDown = new Runnable() { 165 @Override 166 public void run() { 167 mPendingKeyDown = null; 168 key.onPressed(); 169 invalidateKey(key); 170 mListener.onPressKey(key); 171 } 172 }; 173 mHandler.postDelayed(mPendingKeyDown, KEY_PRESS_DELAY_TIME); 174 return false; 175 } 176 177 @Override 178 public void onShowPress(final MotionEvent e) { 179 // User feedback is done at {@link #onDown(MotionEvent)}. 180 } 181 182 @Override 183 public boolean onSingleTapUp(final MotionEvent e) { 184 final Key key = getKey(e); 185 final Runnable pendingKeyDown = mPendingKeyDown; 186 final Key currentKey = mCurrentKey; 187 releaseCurrentKey(false /* withKeyRegistering */); 188 if (key == null) { 189 return false; 190 } 191 if (key == currentKey && pendingKeyDown != null) { 192 pendingKeyDown.run(); 193 // Trigger key-release event a little later so that a user can see visual feedback. 194 mHandler.postDelayed(new Runnable() { 195 @Override 196 public void run() { 197 key.onReleased(); 198 invalidateKey(key); 199 mListener.onReleaseKey(key); 200 } 201 }, KEY_RELEASE_DELAY_TIME); 202 } else { 203 key.onReleased(); 204 invalidateKey(key); 205 mListener.onReleaseKey(key); 206 } 207 return true; 208 } 209 210 @Override 211 public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX, 212 final float distanceY) { 213 releaseCurrentKey(false /* withKeyRegistering */); 214 return false; 215 } 216 217 @Override 218 public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, 219 final float velocityY) { 220 releaseCurrentKey(false /* withKeyRegistering */); 221 return false; 222 } 223 224 @Override 225 public void onLongPress(final MotionEvent e) { 226 // Long press detection of {@link #mGestureDetector} is disabled and not used. 227 } 228} 229