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