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