EmojiPageKeyboardView.java revision dec599d1723f4ff52f066bd2dd1a4457d30cd33c
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 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    }
71
72    public void setOnKeyEventListener(final OnKeyEventListener listener) {
73        mListener = listener;
74    }
75
76    /**
77     * {@inheritDoc}
78     */
79    @Override
80    public void setKeyboard(final Keyboard keyboard) {
81        super.setKeyboard(keyboard);
82        mKeyDetector.setKeyboard(keyboard, 0 /* correctionX */, 0 /* correctionY */);
83        if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
84            if (mAccessibilityDelegate == null) {
85                mAccessibilityDelegate = new KeyboardAccessibilityDelegate<>(this, mKeyDetector);
86            }
87            mAccessibilityDelegate.setKeyboard(keyboard);
88        } else {
89            mAccessibilityDelegate = null;
90        }
91    }
92
93    /**
94     * {@inheritDoc}
95     */
96    @Override
97    public boolean onHoverEvent(final MotionEvent event) {
98        final KeyboardAccessibilityDelegate<EmojiPageKeyboardView> accessibilityDelegate =
99                mAccessibilityDelegate;
100        if (accessibilityDelegate != null) {
101            return accessibilityDelegate.onHoverEvent(event);
102        }
103        return super.onHoverEvent(event);
104    }
105
106    /**
107     * {@inheritDoc}
108     */
109    @Override
110    public boolean onTouchEvent(final MotionEvent e) {
111        if (mGestureDetector.onTouchEvent(e)) {
112            return true;
113        }
114        final Key key = getKey(e);
115        if (key != null && key != mCurrentKey) {
116            releaseCurrentKey(false /* withKeyRegistering */);
117        }
118        return true;
119    }
120
121    // {@link GestureEnabler#OnGestureListener} methods.
122    private Key mCurrentKey;
123    private Runnable mPendingKeyDown;
124    private final Handler mHandler;
125
126    private Key getKey(final MotionEvent e) {
127        final int index = e.getActionIndex();
128        final int x = (int)e.getX(index);
129        final int y = (int)e.getY(index);
130        return mKeyDetector.detectHitKey(x, y);
131    }
132
133    public void releaseCurrentKey(final boolean withKeyRegistering) {
134        mHandler.removeCallbacks(mPendingKeyDown);
135        mPendingKeyDown = null;
136        final Key currentKey = mCurrentKey;
137        if (currentKey == null) {
138            return;
139        }
140        currentKey.onReleased();
141        invalidateKey(currentKey);
142        if (withKeyRegistering) {
143            mListener.onReleaseKey(currentKey);
144        }
145        mCurrentKey = null;
146    }
147
148    @Override
149    public boolean onDown(final MotionEvent e) {
150        final Key key = getKey(e);
151        releaseCurrentKey(false /* withKeyRegistering */);
152        mCurrentKey = key;
153        if (key == null) {
154            return false;
155        }
156        // Do not trigger key-down effect right now in case this is actually a fling action.
157        mPendingKeyDown = new Runnable() {
158            @Override
159            public void run() {
160                mPendingKeyDown = null;
161                key.onPressed();
162                invalidateKey(key);
163                mListener.onPressKey(key);
164            }
165        };
166        mHandler.postDelayed(mPendingKeyDown, KEY_PRESS_DELAY_TIME);
167        return false;
168    }
169
170    @Override
171    public void onShowPress(final MotionEvent e) {
172        // User feedback is done at {@link #onDown(MotionEvent)}.
173    }
174
175    @Override
176    public boolean onSingleTapUp(final MotionEvent e) {
177        final Key key = getKey(e);
178        final Runnable pendingKeyDown = mPendingKeyDown;
179        final Key currentKey = mCurrentKey;
180        releaseCurrentKey(false /* withKeyRegistering */);
181        if (key == null) {
182            return false;
183        }
184        if (key == currentKey && pendingKeyDown != null) {
185            pendingKeyDown.run();
186            // Trigger key-release event a little later so that a user can see visual feedback.
187            mHandler.postDelayed(new Runnable() {
188                @Override
189                public void run() {
190                    key.onReleased();
191                    invalidateKey(key);
192                    mListener.onReleaseKey(key);
193                }
194            }, KEY_RELEASE_DELAY_TIME);
195        } else {
196            key.onReleased();
197            invalidateKey(key);
198            mListener.onReleaseKey(key);
199        }
200        return true;
201    }
202
203    @Override
204    public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX,
205           final float distanceY) {
206        releaseCurrentKey(false /* withKeyRegistering */);
207        return false;
208    }
209
210    @Override
211    public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX,
212            final float velocityY) {
213        releaseCurrentKey(false /* withKeyRegistering */);
214        return false;
215    }
216
217    @Override
218    public void onLongPress(final MotionEvent e) {
219        // Long press detection of {@link #mGestureDetector} is disabled and not used.
220    }
221}
222