/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.inputmethod.keyboard; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.accessibility.MoreKeysKeyboardAccessibilityDelegate; import com.android.inputmethod.keyboard.internal.KeyDrawParams; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.common.Constants; import com.android.inputmethod.latin.utils.CoordinateUtils; /** * A view that renders a virtual {@link MoreKeysKeyboard}. It handles rendering of keys and * detecting key presses and touch movements. */ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel { private final int[] mCoordinates = CoordinateUtils.newInstance(); private final Drawable mDivider; protected final KeyDetector mKeyDetector; private Controller mController = EMPTY_CONTROLLER; protected KeyboardActionListener mListener; private int mOriginX; private int mOriginY; private Key mCurrentKey; private int mActivePointerId; protected MoreKeysKeyboardAccessibilityDelegate mAccessibilityDelegate; public MoreKeysKeyboardView(final Context context, final AttributeSet attrs) { this(context, attrs, R.attr.moreKeysKeyboardViewStyle); } public MoreKeysKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); final TypedArray moreKeysKeyboardViewAttr = context.obtainStyledAttributes(attrs, R.styleable.MoreKeysKeyboardView, defStyle, R.style.MoreKeysKeyboardView); mDivider = moreKeysKeyboardViewAttr.getDrawable(R.styleable.MoreKeysKeyboardView_divider); if (mDivider != null) { // TODO: Drawable itself should have an alpha value. mDivider.setAlpha(128); } moreKeysKeyboardViewAttr.recycle(); mKeyDetector = new MoreKeysDetector(getResources().getDimension( R.dimen.config_more_keys_keyboard_slide_allowance)); } @Override protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { final Keyboard keyboard = getKeyboard(); if (keyboard != null) { final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight(); final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom(); setMeasuredDimension(width, height); } else { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } @Override protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, final KeyDrawParams params) { if (!key.isSpacer() || !(key instanceof MoreKeysKeyboard.MoreKeyDivider) || mDivider == null) { super.onDrawKeyTopVisuals(key, canvas, paint, params); return; } final int keyWidth = key.getDrawWidth(); final int keyHeight = key.getHeight(); final int iconWidth = Math.min(mDivider.getIntrinsicWidth(), keyWidth); final int iconHeight = mDivider.getIntrinsicHeight(); final int iconX = (keyWidth - iconWidth) / 2; // Align horizontally center final int iconY = (keyHeight - iconHeight) / 2; // Align vertically center drawIcon(canvas, mDivider, iconX, iconY, iconWidth, iconHeight); } @Override public void setKeyboard(final Keyboard keyboard) { super.setKeyboard(keyboard); mKeyDetector.setKeyboard( keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection()); if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) { if (mAccessibilityDelegate == null) { mAccessibilityDelegate = new MoreKeysKeyboardAccessibilityDelegate( this, mKeyDetector); mAccessibilityDelegate.setOpenAnnounce(R.string.spoken_open_more_keys_keyboard); mAccessibilityDelegate.setCloseAnnounce(R.string.spoken_close_more_keys_keyboard); } mAccessibilityDelegate.setKeyboard(keyboard); } else { mAccessibilityDelegate = null; } } @Override public void showMoreKeysPanel(final View parentView, final Controller controller, final int pointX, final int pointY, final KeyboardActionListener listener) { mController = controller; mListener = listener; final View container = getContainerView(); // The coordinates of panel's left-top corner in parentView's coordinate system. // We need to consider background drawable paddings. final int x = pointX - getDefaultCoordX() - container.getPaddingLeft() - getPaddingLeft(); final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom() + getPaddingBottom(); parentView.getLocationInWindow(mCoordinates); // Ensure the horizontal position of the panel does not extend past the parentView edges. final int maxX = parentView.getMeasuredWidth() - container.getMeasuredWidth(); final int panelX = Math.max(0, Math.min(maxX, x)) + CoordinateUtils.x(mCoordinates); final int panelY = y + CoordinateUtils.y(mCoordinates); container.setX(panelX); container.setY(panelY); mOriginX = x + container.getPaddingLeft(); mOriginY = y + container.getPaddingTop(); controller.onShowMoreKeysPanel(this); final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate; if (accessibilityDelegate != null && AccessibilityUtils.getInstance().isAccessibilityEnabled()) { accessibilityDelegate.onShowMoreKeysKeyboard(); } } /** * Returns the default x coordinate for showing this panel. */ protected int getDefaultCoordX() { return ((MoreKeysKeyboard)getKeyboard()).getDefaultCoordX(); } @Override public void onDownEvent(final int x, final int y, final int pointerId, final long eventTime) { mActivePointerId = pointerId; mCurrentKey = detectKey(x, y); } @Override public void onMoveEvent(final int x, final int y, final int pointerId, final long eventTime) { if (mActivePointerId != pointerId) { return; } final boolean hasOldKey = (mCurrentKey != null); mCurrentKey = detectKey(x, y); if (hasOldKey && mCurrentKey == null) { // A more keys keyboard is canceled when detecting no key. mController.onCancelMoreKeysPanel(); } } @Override public void onUpEvent(final int x, final int y, final int pointerId, final long eventTime) { if (mActivePointerId != pointerId) { return; } // Calling {@link #detectKey(int,int,int)} here is harmless because the last move event and // the following up event share the same coordinates. mCurrentKey = detectKey(x, y); if (mCurrentKey != null) { updateReleaseKeyGraphics(mCurrentKey); onKeyInput(mCurrentKey, x, y); mCurrentKey = null; } } /** * Performs the specific action for this panel when the user presses a key on the panel. */ protected void onKeyInput(final Key key, final int x, final int y) { final int code = key.getCode(); if (code == Constants.CODE_OUTPUT_TEXT) { mListener.onTextInput(mCurrentKey.getOutputText()); } else if (code != Constants.CODE_UNSPECIFIED) { if (getKeyboard().hasProximityCharsCorrection(code)) { mListener.onCodeInput(code, x, y, false /* isKeyRepeat */); } else { mListener.onCodeInput(code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false /* isKeyRepeat */); } } } private Key detectKey(int x, int y) { final Key oldKey = mCurrentKey; final Key newKey = mKeyDetector.detectHitKey(x, y); if (newKey == oldKey) { return newKey; } // A new key is detected. if (oldKey != null) { updateReleaseKeyGraphics(oldKey); invalidateKey(oldKey); } if (newKey != null) { updatePressKeyGraphics(newKey); invalidateKey(newKey); } return newKey; } private void updateReleaseKeyGraphics(final Key key) { key.onReleased(); invalidateKey(key); } private void updatePressKeyGraphics(final Key key) { key.onPressed(); invalidateKey(key); } @Override public void dismissMoreKeysPanel() { if (!isShowingInParent()) { return; } final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate; if (accessibilityDelegate != null && AccessibilityUtils.getInstance().isAccessibilityEnabled()) { accessibilityDelegate.onDismissMoreKeysKeyboard(); } mController.onDismissMoreKeysPanel(); } @Override public int translateX(final int x) { return x - mOriginX; } @Override public int translateY(final int y) { return y - mOriginY; } @Override public boolean onTouchEvent(final MotionEvent me) { final int action = me.getActionMasked(); final long eventTime = me.getEventTime(); final int index = me.getActionIndex(); final int x = (int)me.getX(index); final int y = (int)me.getY(index); final int pointerId = me.getPointerId(index); switch (action) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: onDownEvent(x, y, pointerId, eventTime); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: onUpEvent(x, y, pointerId, eventTime); break; case MotionEvent.ACTION_MOVE: onMoveEvent(x, y, pointerId, eventTime); break; } return true; } /** * {@inheritDoc} */ @Override public boolean onHoverEvent(final MotionEvent event) { final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate; if (accessibilityDelegate != null && AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { return accessibilityDelegate.onHoverEvent(event); } return super.onHoverEvent(event); } private View getContainerView() { return (View)getParent(); } @Override public void showInParent(final ViewGroup parentView) { removeFromParent(); parentView.addView(getContainerView()); } @Override public void removeFromParent() { final View containerView = getContainerView(); final ViewGroup currentParent = (ViewGroup)containerView.getParent(); if (currentParent != null) { currentParent.removeView(containerView); } } @Override public boolean isShowingInParent() { return (getContainerView().getParent() != null); } }