MoreKeysKeyboardView.java revision 727e818e95ef68460ebafb2eb3b11c23a2d8fcd8
1/*
2 * Copyright (C) 2011 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;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Canvas;
22import android.graphics.Paint;
23import android.graphics.drawable.Drawable;
24import android.util.AttributeSet;
25import android.view.MotionEvent;
26import android.view.View;
27import android.view.ViewGroup;
28
29import com.android.inputmethod.accessibility.AccessibilityUtils;
30import com.android.inputmethod.accessibility.MoreKeysKeyboardAccessibilityDelegate;
31import com.android.inputmethod.keyboard.internal.KeyDrawParams;
32import com.android.inputmethod.latin.Constants;
33import com.android.inputmethod.latin.R;
34import com.android.inputmethod.latin.utils.CoordinateUtils;
35
36/**
37 * A view that renders a virtual {@link MoreKeysKeyboard}. It handles rendering of keys and
38 * detecting key presses and touch movements.
39 */
40public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel {
41    private final int[] mCoordinates = CoordinateUtils.newInstance();
42
43    private final Drawable mDivider;
44    protected final KeyDetector mKeyDetector;
45    private Controller mController = EMPTY_CONTROLLER;
46    protected KeyboardActionListener mListener;
47    private int mOriginX;
48    private int mOriginY;
49    private Key mCurrentKey;
50
51    private int mActivePointerId;
52
53    protected MoreKeysKeyboardAccessibilityDelegate mAccessibilityDelegate;
54
55    public MoreKeysKeyboardView(final Context context, final AttributeSet attrs) {
56        this(context, attrs, R.attr.moreKeysKeyboardViewStyle);
57    }
58
59    public MoreKeysKeyboardView(final Context context, final AttributeSet attrs,
60            final int defStyle) {
61        super(context, attrs, defStyle);
62        final TypedArray moreKeysKeyboardViewAttr = context.obtainStyledAttributes(attrs,
63                R.styleable.MoreKeysKeyboardView, defStyle, R.style.MoreKeysKeyboardView);
64        mDivider = moreKeysKeyboardViewAttr.getDrawable(R.styleable.MoreKeysKeyboardView_divider);
65        if (mDivider != null) {
66            // TODO: Drawable itself should have an alpha value.
67            mDivider.setAlpha(128);
68        }
69        moreKeysKeyboardViewAttr.recycle();
70        mKeyDetector = new MoreKeysDetector(getResources().getDimension(
71                R.dimen.config_more_keys_keyboard_slide_allowance));
72    }
73
74    @Override
75    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
76        final Keyboard keyboard = getKeyboard();
77        if (keyboard != null) {
78            final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
79            final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
80            setMeasuredDimension(width, height);
81        } else {
82            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
83        }
84    }
85
86    @Override
87    protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
88            final KeyDrawParams params) {
89        if (!key.isSpacer() || !(key instanceof MoreKeysKeyboard.MoreKeyDivider)
90                || mDivider == null) {
91            super.onDrawKeyTopVisuals(key, canvas, paint, params);
92            return;
93        }
94        final int keyWidth = key.getDrawWidth();
95        final int keyHeight = key.getHeight();
96        final int iconWidth = Math.min(mDivider.getIntrinsicWidth(), keyWidth);
97        final int iconHeight = mDivider.getIntrinsicHeight();
98        final int iconX = (keyWidth - iconWidth) / 2; // Align horizontally center
99        final int iconY = (keyHeight - iconHeight) / 2; // Align vertically center
100        drawIcon(canvas, mDivider, iconX, iconY, iconWidth, iconHeight);
101    }
102
103    @Override
104    public void setKeyboard(final Keyboard keyboard) {
105        super.setKeyboard(keyboard);
106        mKeyDetector.setKeyboard(
107                keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
108        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
109            if (mAccessibilityDelegate == null) {
110                mAccessibilityDelegate = new MoreKeysKeyboardAccessibilityDelegate(
111                        this, mKeyDetector);
112                mAccessibilityDelegate.setOpenAnnounce(R.string.spoken_open_more_keys_keyboard);
113                mAccessibilityDelegate.setCloseAnnounce(R.string.spoken_close_more_keys_keyboard);
114            }
115            mAccessibilityDelegate.setKeyboard(keyboard);
116        } else {
117            mAccessibilityDelegate = null;
118        }
119    }
120
121    @Override
122    public void showMoreKeysPanel(final View parentView, final Controller controller,
123            final int pointX, final int pointY, final KeyboardActionListener listener) {
124        mController = controller;
125        mListener = listener;
126        final View container = getContainerView();
127        // The coordinates of panel's left-top corner in parentView's coordinate system.
128        // We need to consider background drawable paddings.
129        final int x = pointX - getDefaultCoordX() - container.getPaddingLeft() - getPaddingLeft();
130        final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom()
131                + getPaddingBottom();
132
133        parentView.getLocationInWindow(mCoordinates);
134        // Ensure the horizontal position of the panel does not extend past the parentView edges.
135        final int maxX = parentView.getMeasuredWidth() - container.getMeasuredWidth();
136        final int panelX = Math.max(0, Math.min(maxX, x)) + CoordinateUtils.x(mCoordinates);
137        final int panelY = y + CoordinateUtils.y(mCoordinates);
138        container.setX(panelX);
139        container.setY(panelY);
140
141        mOriginX = x + container.getPaddingLeft();
142        mOriginY = y + container.getPaddingTop();
143        controller.onShowMoreKeysPanel(this);
144        final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
145        if (accessibilityDelegate != null) {
146            accessibilityDelegate.onShowMoreKeysKeyboard();
147        }
148    }
149
150    /**
151     * Returns the default x coordinate for showing this panel.
152     */
153    protected int getDefaultCoordX() {
154        return ((MoreKeysKeyboard)getKeyboard()).getDefaultCoordX();
155    }
156
157    @Override
158    public void onDownEvent(final int x, final int y, final int pointerId, final long eventTime) {
159        mActivePointerId = pointerId;
160        mCurrentKey = detectKey(x, y);
161    }
162
163    @Override
164    public void onMoveEvent(final int x, final int y, final int pointerId, final long eventTime) {
165        if (mActivePointerId != pointerId) {
166            return;
167        }
168        final boolean hasOldKey = (mCurrentKey != null);
169        mCurrentKey = detectKey(x, y);
170        if (hasOldKey && mCurrentKey == null) {
171            // A more keys keyboard is canceled when detecting no key.
172            mController.onCancelMoreKeysPanel();
173        }
174    }
175
176    @Override
177    public void onUpEvent(final int x, final int y, final int pointerId, final long eventTime) {
178        if (mActivePointerId != pointerId) {
179            return;
180        }
181        // Calling {@link #detectKey(int,int,int)} here is harmless because the last move event and
182        // the following up event share the same coordinates.
183        mCurrentKey = detectKey(x, y);
184        if (mCurrentKey != null) {
185            updateReleaseKeyGraphics(mCurrentKey);
186            onKeyInput(mCurrentKey, x, y);
187            mCurrentKey = null;
188        }
189    }
190
191    /**
192     * Performs the specific action for this panel when the user presses a key on the panel.
193     */
194    protected void onKeyInput(final Key key, final int x, final int y) {
195        final int code = key.getCode();
196        if (code == Constants.CODE_OUTPUT_TEXT) {
197            mListener.onTextInput(mCurrentKey.getOutputText());
198        } else if (code != Constants.CODE_UNSPECIFIED) {
199            if (getKeyboard().hasProximityCharsCorrection(code)) {
200                mListener.onCodeInput(code, x, y, false /* isKeyRepeat */);
201            } else {
202                mListener.onCodeInput(code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
203                        false /* isKeyRepeat */);
204            }
205        }
206    }
207
208    private Key detectKey(int x, int y) {
209        final Key oldKey = mCurrentKey;
210        final Key newKey = mKeyDetector.detectHitKey(x, y);
211        if (newKey == oldKey) {
212            return newKey;
213        }
214        // A new key is detected.
215        if (oldKey != null) {
216            updateReleaseKeyGraphics(oldKey);
217            invalidateKey(oldKey);
218        }
219        if (newKey != null) {
220            updatePressKeyGraphics(newKey);
221            invalidateKey(newKey);
222        }
223        return newKey;
224    }
225
226    private void updateReleaseKeyGraphics(final Key key) {
227        key.onReleased();
228        invalidateKey(key);
229    }
230
231    private void updatePressKeyGraphics(final Key key) {
232        key.onPressed();
233        invalidateKey(key);
234    }
235
236    @Override
237    public void dismissMoreKeysPanel() {
238        if (!isShowingInParent()) {
239            return;
240        }
241        final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
242        if (accessibilityDelegate != null) {
243            accessibilityDelegate.onDismissMoreKeysKeyboard();
244        }
245        mController.onDismissMoreKeysPanel();
246    }
247
248    @Override
249    public int translateX(final int x) {
250        return x - mOriginX;
251    }
252
253    @Override
254    public int translateY(final int y) {
255        return y - mOriginY;
256    }
257
258    @Override
259    public boolean onTouchEvent(final MotionEvent me) {
260        final int action = me.getActionMasked();
261        final long eventTime = me.getEventTime();
262        final int index = me.getActionIndex();
263        final int x = (int)me.getX(index);
264        final int y = (int)me.getY(index);
265        final int pointerId = me.getPointerId(index);
266        switch (action) {
267        case MotionEvent.ACTION_DOWN:
268        case MotionEvent.ACTION_POINTER_DOWN:
269            onDownEvent(x, y, pointerId, eventTime);
270            break;
271        case MotionEvent.ACTION_UP:
272        case MotionEvent.ACTION_POINTER_UP:
273            onUpEvent(x, y, pointerId, eventTime);
274            break;
275        case MotionEvent.ACTION_MOVE:
276            onMoveEvent(x, y, pointerId, eventTime);
277            break;
278        }
279        return true;
280    }
281
282    /**
283     * {@inheritDoc}
284     */
285    @Override
286    public boolean onHoverEvent(final MotionEvent event) {
287        final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
288        if (accessibilityDelegate != null) {
289            return accessibilityDelegate.onHoverEvent(event);
290        }
291        return super.onHoverEvent(event);
292    }
293
294    private View getContainerView() {
295        return (View)getParent();
296    }
297
298    @Override
299    public void showInParent(final ViewGroup parentView) {
300        removeFromParent();
301        parentView.addView(getContainerView());
302    }
303
304    @Override
305    public void removeFromParent() {
306        final View containerView = getContainerView();
307        final ViewGroup currentParent = (ViewGroup)containerView.getParent();
308        if (currentParent != null) {
309            currentParent.removeView(containerView);
310        }
311    }
312
313    @Override
314    public boolean isShowingInParent() {
315        return (getContainerView().getParent() != null);
316    }
317}
318