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().isAccessibilityEnabled()) {
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                && AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
147            accessibilityDelegate.onShowMoreKeysKeyboard();
148        }
149    }
150
151    /**
152     * Returns the default x coordinate for showing this panel.
153     */
154    protected int getDefaultCoordX() {
155        return ((MoreKeysKeyboard)getKeyboard()).getDefaultCoordX();
156    }
157
158    @Override
159    public void onDownEvent(final int x, final int y, final int pointerId, final long eventTime) {
160        mActivePointerId = pointerId;
161        mCurrentKey = detectKey(x, y);
162    }
163
164    @Override
165    public void onMoveEvent(final int x, final int y, final int pointerId, final long eventTime) {
166        if (mActivePointerId != pointerId) {
167            return;
168        }
169        final boolean hasOldKey = (mCurrentKey != null);
170        mCurrentKey = detectKey(x, y);
171        if (hasOldKey && mCurrentKey == null) {
172            // A more keys keyboard is canceled when detecting no key.
173            mController.onCancelMoreKeysPanel();
174        }
175    }
176
177    @Override
178    public void onUpEvent(final int x, final int y, final int pointerId, final long eventTime) {
179        if (mActivePointerId != pointerId) {
180            return;
181        }
182        // Calling {@link #detectKey(int,int,int)} here is harmless because the last move event and
183        // the following up event share the same coordinates.
184        mCurrentKey = detectKey(x, y);
185        if (mCurrentKey != null) {
186            updateReleaseKeyGraphics(mCurrentKey);
187            onKeyInput(mCurrentKey, x, y);
188            mCurrentKey = null;
189        }
190    }
191
192    /**
193     * Performs the specific action for this panel when the user presses a key on the panel.
194     */
195    protected void onKeyInput(final Key key, final int x, final int y) {
196        final int code = key.getCode();
197        if (code == Constants.CODE_OUTPUT_TEXT) {
198            mListener.onTextInput(mCurrentKey.getOutputText());
199        } else if (code != Constants.CODE_UNSPECIFIED) {
200            if (getKeyboard().hasProximityCharsCorrection(code)) {
201                mListener.onCodeInput(code, x, y, false /* isKeyRepeat */);
202            } else {
203                mListener.onCodeInput(code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
204                        false /* isKeyRepeat */);
205            }
206        }
207    }
208
209    private Key detectKey(int x, int y) {
210        final Key oldKey = mCurrentKey;
211        final Key newKey = mKeyDetector.detectHitKey(x, y);
212        if (newKey == oldKey) {
213            return newKey;
214        }
215        // A new key is detected.
216        if (oldKey != null) {
217            updateReleaseKeyGraphics(oldKey);
218            invalidateKey(oldKey);
219        }
220        if (newKey != null) {
221            updatePressKeyGraphics(newKey);
222            invalidateKey(newKey);
223        }
224        return newKey;
225    }
226
227    private void updateReleaseKeyGraphics(final Key key) {
228        key.onReleased();
229        invalidateKey(key);
230    }
231
232    private void updatePressKeyGraphics(final Key key) {
233        key.onPressed();
234        invalidateKey(key);
235    }
236
237    @Override
238    public void dismissMoreKeysPanel() {
239        if (!isShowingInParent()) {
240            return;
241        }
242        final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
243        if (accessibilityDelegate != null
244                && AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
245            accessibilityDelegate.onDismissMoreKeysKeyboard();
246        }
247        mController.onDismissMoreKeysPanel();
248    }
249
250    @Override
251    public int translateX(final int x) {
252        return x - mOriginX;
253    }
254
255    @Override
256    public int translateY(final int y) {
257        return y - mOriginY;
258    }
259
260    @Override
261    public boolean onTouchEvent(final MotionEvent me) {
262        final int action = me.getActionMasked();
263        final long eventTime = me.getEventTime();
264        final int index = me.getActionIndex();
265        final int x = (int)me.getX(index);
266        final int y = (int)me.getY(index);
267        final int pointerId = me.getPointerId(index);
268        switch (action) {
269        case MotionEvent.ACTION_DOWN:
270        case MotionEvent.ACTION_POINTER_DOWN:
271            onDownEvent(x, y, pointerId, eventTime);
272            break;
273        case MotionEvent.ACTION_UP:
274        case MotionEvent.ACTION_POINTER_UP:
275            onUpEvent(x, y, pointerId, eventTime);
276            break;
277        case MotionEvent.ACTION_MOVE:
278            onMoveEvent(x, y, pointerId, eventTime);
279            break;
280        }
281        return true;
282    }
283
284    /**
285     * {@inheritDoc}
286     */
287    @Override
288    public boolean onHoverEvent(final MotionEvent event) {
289        final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
290        if (accessibilityDelegate != null
291                && AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
292            return accessibilityDelegate.onHoverEvent(event);
293        }
294        return super.onHoverEvent(event);
295    }
296
297    private View getContainerView() {
298        return (View)getParent();
299    }
300
301    @Override
302    public void showInParent(final ViewGroup parentView) {
303        removeFromParent();
304        parentView.addView(getContainerView());
305    }
306
307    @Override
308    public void removeFromParent() {
309        final View containerView = getContainerView();
310        final ViewGroup currentParent = (ViewGroup)containerView.getParent();
311        if (currentParent != null) {
312            currentParent.removeView(containerView);
313        }
314    }
315
316    @Override
317    public boolean isShowingInParent() {
318        return (getContainerView().getParent() != null);
319    }
320}
321