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