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 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