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 
17 package com.android.inputmethod.keyboard;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.Canvas;
22 import android.graphics.Paint;
23 import android.graphics.drawable.Drawable;
24 import android.util.AttributeSet;
25 import android.view.MotionEvent;
26 import android.view.View;
27 import android.view.ViewGroup;
28 
29 import com.android.inputmethod.accessibility.AccessibilityUtils;
30 import com.android.inputmethod.accessibility.MoreKeysKeyboardAccessibilityDelegate;
31 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
32 import com.android.inputmethod.latin.R;
33 import com.android.inputmethod.latin.common.Constants;
34 import com.android.inputmethod.latin.common.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  */
40 public 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 
MoreKeysKeyboardView(final Context context, final AttributeSet attrs)55     public MoreKeysKeyboardView(final Context context, final AttributeSet attrs) {
56         this(context, attrs, R.attr.moreKeysKeyboardViewStyle);
57     }
58 
MoreKeysKeyboardView(final Context context, final AttributeSet attrs, final int defStyle)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
onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)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
onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, final KeyDrawParams params)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
setKeyboard(final Keyboard keyboard)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
showMoreKeysPanel(final View parentView, final Controller controller, final int pointX, final int pointY, final KeyboardActionListener listener)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      */
getDefaultCoordX()154     protected int getDefaultCoordX() {
155         return ((MoreKeysKeyboard)getKeyboard()).getDefaultCoordX();
156     }
157 
158     @Override
onDownEvent(final int x, final int y, final int pointerId, final long eventTime)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
onMoveEvent(final int x, final int y, final int pointerId, final long eventTime)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
onUpEvent(final int x, final int y, final int pointerId, final long eventTime)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      */
onKeyInput(final Key key, final int x, final int y)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 
detectKey(int x, int y)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 
updateReleaseKeyGraphics(final Key key)227     private void updateReleaseKeyGraphics(final Key key) {
228         key.onReleased();
229         invalidateKey(key);
230     }
231 
updatePressKeyGraphics(final Key key)232     private void updatePressKeyGraphics(final Key key) {
233         key.onPressed();
234         invalidateKey(key);
235     }
236 
237     @Override
dismissMoreKeysPanel()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
translateX(final int x)251     public int translateX(final int x) {
252         return x - mOriginX;
253     }
254 
255     @Override
translateY(final int y)256     public int translateY(final int y) {
257         return y - mOriginY;
258     }
259 
260     @Override
onTouchEvent(final MotionEvent me)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
onHoverEvent(final MotionEvent event)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 
getContainerView()297     private View getContainerView() {
298         return (View)getParent();
299     }
300 
301     @Override
showInParent(final ViewGroup parentView)302     public void showInParent(final ViewGroup parentView) {
303         removeFromParent();
304         parentView.addView(getContainerView());
305     }
306 
307     @Override
removeFromParent()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
isShowingInParent()317     public boolean isShowingInParent() {
318         return (getContainerView().getParent() != null);
319     }
320 }
321