From 5a309f57155fb95667c2ccdda730eaf175de8876 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Thu, 2 Dec 2010 18:46:21 +0900 Subject: Move some inner static class to top class in new package This change introduces new package com.android.inputmethod.keyboard which hosts keyboard related classes. Also adds missing @Override and @SuppressWarning("unused") annotation Change-Id: I0317f5cfa317e13f3c77815f48ffc69b5d1ade05 --- java/src/com/android/inputmethod/keyboard/Key.java | 305 ++++ .../android/inputmethod/keyboard/KeyDetector.java | 112 ++ .../android/inputmethod/keyboard/KeyStyles.java | 246 +++ .../com/android/inputmethod/keyboard/Keyboard.java | 386 +++++ .../keyboard/KeyboardActionListener.java | 99 ++ .../android/inputmethod/keyboard/KeyboardId.java | 148 ++ .../inputmethod/keyboard/KeyboardParser.java | 569 +++++++ .../android/inputmethod/keyboard/KeyboardView.java | 1469 ++++++++++++++++++ .../com/android/inputmethod/keyboard/LatinKey.java | 90 ++ .../inputmethod/keyboard/LatinKeyboard.java | 543 +++++++ .../keyboard/LatinKeyboardShiftState.java | 107 ++ .../inputmethod/keyboard/LatinKeyboardView.java | 369 +++++ .../keyboard/MiniKeyboardKeyDetector.java | 58 + .../inputmethod/keyboard/PointerTracker.java | 546 +++++++ .../inputmethod/keyboard/PointerTrackerQueue.java | 67 + .../inputmethod/keyboard/ProximityKeyDetector.java | 75 + java/src/com/android/inputmethod/keyboard/Row.java | 73 + .../keyboard/SlidingLocaleDrawable.java | 163 ++ .../android/inputmethod/keyboard/SwipeTracker.java | 157 ++ .../android/inputmethod/latin/BaseKeyboard.java | 717 --------- .../inputmethod/latin/BaseKeyboardParser.java | 571 ------- .../inputmethod/latin/BaseKeyboardView.java | 1585 -------------------- .../inputmethod/latin/InputLanguageSelection.java | 1 + .../com/android/inputmethod/latin/KeyDetector.java | 112 -- .../com/android/inputmethod/latin/KeyStyles.java | 238 --- .../inputmethod/latin/KeyboardSwitcher.java | 147 +- .../com/android/inputmethod/latin/LatinIME.java | 116 +- .../inputmethod/latin/LatinIMEDebugSettings.java | 1 + .../inputmethod/latin/LatinIMESettings.java | 3 + .../android/inputmethod/latin/LatinIMEUtil.java | 3 + .../android/inputmethod/latin/LatinImeLogger.java | 4 +- .../android/inputmethod/latin/LatinKeyboard.java | 720 --------- .../inputmethod/latin/LatinKeyboardShiftState.java | 89 -- .../inputmethod/latin/LatinKeyboardView.java | 376 ----- .../inputmethod/latin/MiniKeyboardKeyDetector.java | 60 - .../android/inputmethod/latin/PointerTracker.java | 546 ------- .../inputmethod/latin/ProximityKeyDetector.java | 77 - .../android/inputmethod/latin/SubtypeSwitcher.java | 5 +- .../src/com/android/inputmethod/latin/Suggest.java | 1 + .../android/inputmethod/latin/SwipeTracker.java | 157 -- .../android/inputmethod/latin/TextEntryState.java | 2 +- .../com/android/inputmethod/latin/Tutorial.java | 4 + .../android/inputmethod/latin/UserDictionary.java | 1 + .../android/inputmethod/voice/FieldContext.java | 2 + .../android/inputmethod/voice/RecognitionView.java | 8 + .../inputmethod/voice/VoiceIMEConnector.java | 3 +- .../com/android/inputmethod/voice/VoiceInput.java | 11 + .../inputmethod/voice/VoiceInputLogger.java | 1 + 48 files changed, 5708 insertions(+), 5435 deletions(-) create mode 100644 java/src/com/android/inputmethod/keyboard/Key.java create mode 100644 java/src/com/android/inputmethod/keyboard/KeyDetector.java create mode 100644 java/src/com/android/inputmethod/keyboard/KeyStyles.java create mode 100644 java/src/com/android/inputmethod/keyboard/Keyboard.java create mode 100644 java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java create mode 100644 java/src/com/android/inputmethod/keyboard/KeyboardId.java create mode 100644 java/src/com/android/inputmethod/keyboard/KeyboardParser.java create mode 100644 java/src/com/android/inputmethod/keyboard/KeyboardView.java create mode 100644 java/src/com/android/inputmethod/keyboard/LatinKey.java create mode 100644 java/src/com/android/inputmethod/keyboard/LatinKeyboard.java create mode 100644 java/src/com/android/inputmethod/keyboard/LatinKeyboardShiftState.java create mode 100644 java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java create mode 100644 java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java create mode 100644 java/src/com/android/inputmethod/keyboard/PointerTracker.java create mode 100644 java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java create mode 100644 java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java create mode 100644 java/src/com/android/inputmethod/keyboard/Row.java create mode 100644 java/src/com/android/inputmethod/keyboard/SlidingLocaleDrawable.java create mode 100644 java/src/com/android/inputmethod/keyboard/SwipeTracker.java delete mode 100644 java/src/com/android/inputmethod/latin/BaseKeyboard.java delete mode 100644 java/src/com/android/inputmethod/latin/BaseKeyboardParser.java delete mode 100644 java/src/com/android/inputmethod/latin/BaseKeyboardView.java delete mode 100644 java/src/com/android/inputmethod/latin/KeyDetector.java delete mode 100644 java/src/com/android/inputmethod/latin/KeyStyles.java delete mode 100644 java/src/com/android/inputmethod/latin/LatinKeyboard.java delete mode 100644 java/src/com/android/inputmethod/latin/LatinKeyboardShiftState.java delete mode 100644 java/src/com/android/inputmethod/latin/LatinKeyboardView.java delete mode 100644 java/src/com/android/inputmethod/latin/MiniKeyboardKeyDetector.java delete mode 100644 java/src/com/android/inputmethod/latin/PointerTracker.java delete mode 100644 java/src/com/android/inputmethod/latin/ProximityKeyDetector.java delete mode 100644 java/src/com/android/inputmethod/latin/SwipeTracker.java (limited to 'java/src/com/android/inputmethod') diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java new file mode 100644 index 000000000..9b7937618 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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 com.android.inputmethod.keyboard.KeyboardParser.ParseException; +import com.android.inputmethod.keyboard.KeyStyles.KeyStyle; +import com.android.inputmethod.latin.R; + +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.util.Xml; + +/** + * Class for describing the position and characteristics of a single key in the keyboard. + */ +public class Key { + /** + * All the key codes (unicode or custom code) that this key could generate, zero'th + * being the most important. + */ + public int[] codes; + /** The unicode that this key generates in manual temporary upper case mode. */ + public int manualTemporaryUpperCaseCode; + + /** Label to display */ + public CharSequence label; + /** Option of the label */ + public int labelOption; + + /** Icon to display instead of a label. Icon takes precedence over a label */ + public Drawable icon; + /** Hint icon to display on the key in conjunction with the label */ + public Drawable hintIcon; + /** Preview version of the icon, for the preview popup */ + /** + * The hint icon to display on the key when keyboard is in manual temporary upper case + * mode. + */ + public Drawable manualTemporaryUpperCaseHintIcon; + + public Drawable iconPreview; + /** Width of the key, not including the gap */ + public int width; + /** Height of the key, not including the gap */ + public int height; + /** The horizontal gap before this key */ + public int gap; + /** Whether this key is sticky, i.e., a toggle key */ + public boolean sticky; + /** X coordinate of the key in the keyboard layout */ + public int x; + /** Y coordinate of the key in the keyboard layout */ + public int y; + /** The current pressed state of this key */ + public boolean pressed; + /** If this is a sticky key, is it on? */ + public boolean on; + /** Text to output when pressed. This can be multiple characters, like ".com" */ + public CharSequence text; + /** Popup characters */ + public CharSequence popupCharacters; + + /** + * Flags that specify the anchoring to edges of the keyboard for detecting touch events + * that are just out of the boundary of the key. This is a bit mask of + * {@link Keyboard#EDGE_LEFT}, {@link Keyboard#EDGE_RIGHT}, + * {@link Keyboard#EDGE_TOP} and {@link Keyboard#EDGE_BOTTOM}. + */ + public int edgeFlags; + /** Whether this is a modifier key, such as Shift or Alt */ + public boolean modifier; + /** The Keyboard that this key belongs to */ + protected final Keyboard keyboard; + /** + * If this key pops up a mini keyboard, this is the resource id for the XML layout for that + * keyboard. + */ + public int popupResId; + /** Whether this key repeats itself when held down */ + public boolean repeatable; + + + private final static int[] KEY_STATE_NORMAL_ON = { + android.R.attr.state_checkable, + android.R.attr.state_checked + }; + + private final static int[] KEY_STATE_PRESSED_ON = { + android.R.attr.state_pressed, + android.R.attr.state_checkable, + android.R.attr.state_checked + }; + + private final static int[] KEY_STATE_NORMAL_OFF = { + android.R.attr.state_checkable + }; + + private final static int[] KEY_STATE_PRESSED_OFF = { + android.R.attr.state_pressed, + android.R.attr.state_checkable + }; + + private final static int[] KEY_STATE_NORMAL = { + }; + + private final static int[] KEY_STATE_PRESSED = { + android.R.attr.state_pressed + }; + + /** Create an empty key with no attributes. */ + public Key(Row parent) { + keyboard = parent.parent; + height = parent.defaultHeight; + gap = parent.defaultHorizontalGap; + width = parent.defaultWidth - gap; + edgeFlags = parent.rowEdgeFlags; + } + + /** Create a key with the given top-left coordinate and extract its attributes from + * the XML parser. + * @param res resources associated with the caller's context + * @param parent the row that this key belongs to. The row must already be attached to + * a {@link Keyboard}. + * @param x the x coordinate of the top-left + * @param y the y coordinate of the top-left + * @param parser the XML parser containing the attributes for this key + */ + public Key(Resources res, Row parent, int x, int y, XmlResourceParser parser, + KeyStyles keyStyles) { + this(parent); + + TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard); + height = KeyboardParser.getDimensionOrFraction(a, + R.styleable.Keyboard_keyHeight, + keyboard.mDisplayHeight, parent.defaultHeight); + gap = KeyboardParser.getDimensionOrFraction(a, + R.styleable.Keyboard_horizontalGap, + keyboard.mDisplayWidth, parent.defaultHorizontalGap); + width = KeyboardParser.getDimensionOrFraction(a, + R.styleable.Keyboard_keyWidth, + keyboard.mDisplayWidth, parent.defaultWidth) - gap; + a.recycle(); + + a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard_Key); + + final KeyStyle style; + if (a.hasValue(R.styleable.Keyboard_Key_keyStyle)) { + String styleName = a.getString(R.styleable.Keyboard_Key_keyStyle); + style = keyStyles.getKeyStyle(styleName); + if (style == null) + throw new ParseException("Unknown key style: " + styleName, parser); + } else { + style = keyStyles.getEmptyKeyStyle(); + } + + // Horizontal gap is divided equally to both sides of the key. + this.x = x + gap / 2; + this.y = y; + + codes = style.getIntArray(a, R.styleable.Keyboard_Key_codes); + iconPreview = style.getDrawable(a, R.styleable.Keyboard_Key_iconPreview); + Keyboard.setDefaultBounds(iconPreview); + popupCharacters = style.getText(a, R.styleable.Keyboard_Key_popupCharacters); + popupResId = style.getResourceId(a, R.styleable.Keyboard_Key_popupKeyboard, 0); + repeatable = style.getBoolean(a, R.styleable.Keyboard_Key_isRepeatable, false); + modifier = style.getBoolean(a, R.styleable.Keyboard_Key_isModifier, false); + sticky = style.getBoolean(a, R.styleable.Keyboard_Key_isSticky, false); + edgeFlags = style.getFlag(a, R.styleable.Keyboard_Key_keyEdgeFlags, 0); + edgeFlags |= parent.rowEdgeFlags; + + icon = style.getDrawable(a, R.styleable.Keyboard_Key_keyIcon); + Keyboard.setDefaultBounds(icon); + hintIcon = style.getDrawable(a, R.styleable.Keyboard_Key_keyHintIcon); + Keyboard.setDefaultBounds(hintIcon); + manualTemporaryUpperCaseHintIcon = style.getDrawable(a, + R.styleable.Keyboard_Key_manualTemporaryUpperCaseHintIcon); + Keyboard.setDefaultBounds(manualTemporaryUpperCaseHintIcon); + + label = style.getText(a, R.styleable.Keyboard_Key_keyLabel); + labelOption = style.getFlag(a, R.styleable.Keyboard_Key_keyLabelOption, 0); + manualTemporaryUpperCaseCode = style.getInt(a, + R.styleable.Keyboard_Key_manualTemporaryUpperCaseCode, 0); + text = style.getText(a, R.styleable.Keyboard_Key_keyOutputText); + final Drawable shiftedIcon = style.getDrawable(a, + R.styleable.Keyboard_Key_shiftedIcon); + if (shiftedIcon != null) + keyboard.getShiftedIcons().put(this, shiftedIcon); + + if (codes == null && !TextUtils.isEmpty(label)) { + codes = new int[] { label.charAt(0) }; + } + a.recycle(); + } + + /** + * Informs the key that it has been pressed, in case it needs to change its appearance or + * state. + * @see #onReleased(boolean) + */ + public void onPressed() { + pressed = !pressed; + } + + /** + * Changes the pressed state of the key. If it is a sticky key, it will also change the + * toggled state of the key if the finger was release inside. + * @param inside whether the finger was released inside the key + * @see #onPressed() + */ + public void onReleased(boolean inside) { + pressed = !pressed; + if (sticky) { + on = !on; + } + } + + /** + * Detects if a point falls inside this key. + * @param x the x-coordinate of the point + * @param y the y-coordinate of the point + * @return whether or not the point falls inside the key. If the key is attached to an + * edge, it will assume that all points between the key and the edge are considered to be + * inside the key. + */ + public boolean isInside(int x, int y) { + boolean leftEdge = (edgeFlags & Keyboard.EDGE_LEFT) > 0; + boolean rightEdge = (edgeFlags & Keyboard.EDGE_RIGHT) > 0; + boolean topEdge = (edgeFlags & Keyboard.EDGE_TOP) > 0; + boolean bottomEdge = (edgeFlags & Keyboard.EDGE_BOTTOM) > 0; + if ((x >= this.x || (leftEdge && x <= this.x + this.width)) + && (x < this.x + this.width || (rightEdge && x >= this.x)) + && (y >= this.y || (topEdge && y <= this.y + this.height)) + && (y < this.y + this.height || (bottomEdge && y >= this.y))) { + return true; + } else { + return false; + } + } + + /** + * Returns the square of the distance to the nearest edge of the key and the given point. + * @param x the x-coordinate of the point + * @param y the y-coordinate of the point + * @return the square of the distance of the point from the nearest edge of the key + */ + public int squaredDistanceToEdge(int x, int y) { + final int left = this.x; + final int right = left + this.width; + final int top = this.y; + final int bottom = top + this.height; + final int edgeX = x < left ? left : (x > right ? right : x); + final int edgeY = y < top ? top : (y > bottom ? bottom : y); + final int dx = x - edgeX; + final int dy = y - edgeY; + return dx * dx + dy * dy; + } + + /** + * Returns the drawable state for the key, based on the current state and type of the key. + * @return the drawable state of the key. + * @see android.graphics.drawable.StateListDrawable#setState(int[]) + */ + public int[] getCurrentDrawableState() { + int[] states = KEY_STATE_NORMAL; + + if (on) { + if (pressed) { + states = KEY_STATE_PRESSED_ON; + } else { + states = KEY_STATE_NORMAL_ON; + } + } else { + if (sticky) { + if (pressed) { + states = KEY_STATE_PRESSED_OFF; + } else { + states = KEY_STATE_NORMAL_OFF; + } + } else { + if (pressed) { + states = KEY_STATE_PRESSED; + } + } + } + return states; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java new file mode 100644 index 000000000..777a79520 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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 java.util.Arrays; +import java.util.List; + +public abstract class KeyDetector { + public static final int NOT_A_KEY = -1; + + protected Keyboard mKeyboard; + + private Key[] mKeys; + + protected int mCorrectionX; + + protected int mCorrectionY; + + protected boolean mProximityCorrectOn; + + protected int mProximityThresholdSquare; + + public Key[] setKeyboard(Keyboard keyboard, float correctionX, float correctionY) { + if (keyboard == null) + throw new NullPointerException(); + mCorrectionX = (int)correctionX; + mCorrectionY = (int)correctionY; + mKeyboard = keyboard; + List keys = mKeyboard.getKeys(); + Key[] array = keys.toArray(new Key[keys.size()]); + mKeys = array; + return array; + } + + protected int getTouchX(int x) { + return x + mCorrectionX; + } + + protected int getTouchY(int y) { + return y + mCorrectionY; + } + + protected Key[] getKeys() { + if (mKeys == null) + throw new IllegalStateException("keyboard isn't set"); + // mKeyboard is guaranteed not to be null at setKeybaord() method if mKeys is not null + return mKeys; + } + + public void setProximityCorrectionEnabled(boolean enabled) { + mProximityCorrectOn = enabled; + } + + public boolean isProximityCorrectionEnabled() { + return mProximityCorrectOn; + } + + public void setProximityThreshold(int threshold) { + mProximityThresholdSquare = threshold * threshold; + } + + /** + * Allocates array that can hold all key indices returned by {@link #getKeyIndexAndNearbyCodes} + * method. The maximum size of the array should be computed by {@link #getMaxNearbyKeys}. + * + * @return Allocates and returns an array that can hold all key indices returned by + * {@link #getKeyIndexAndNearbyCodes} method. All elements in the returned array are + * initialized by {@link com.android.inputmethod.latin.LatinKeyboardView.NOT_A_KEY} + * value. + */ + public int[] newCodeArray() { + int[] codes = new int[getMaxNearbyKeys()]; + Arrays.fill(codes, NOT_A_KEY); + return codes; + } + + /** + * Computes maximum size of the array that can contain all nearby key indices returned by + * {@link #getKeyIndexAndNearbyCodes}. + * + * @return Returns maximum size of the array that can contain all nearby key indices returned + * by {@link #getKeyIndexAndNearbyCodes}. + */ + abstract protected int getMaxNearbyKeys(); + + /** + * Finds all possible nearby key indices around a touch event point and returns the nearest key + * index. The algorithm to determine the nearby keys depends on the threshold set by + * {@link #setProximityThreshold(int)} and the mode set by + * {@link #setProximityCorrectionEnabled(boolean)}. + * + * @param x The x-coordinate of a touch point + * @param y The y-coordinate of a touch point + * @param allKeys All nearby key indices are returned in this array + * @return The nearest key index + */ + abstract public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys); +} diff --git a/java/src/com/android/inputmethod/keyboard/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/KeyStyles.java new file mode 100644 index 000000000..daa9c86d1 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/KeyStyles.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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 com.android.inputmethod.keyboard.KeyboardParser.ParseException; +import com.android.inputmethod.latin.R; + +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; +import android.util.Log; +import android.util.TypedValue; + +import java.util.HashMap; +import java.util.StringTokenizer; + +public class KeyStyles { + private static final String TAG = "KeyStyles"; + + private final HashMap mStyles = + new HashMap(); + private static final KeyStyle EMPTY_KEY_STYLE = new EmptyKeyStyle(); + + public interface KeyStyle { + public int[] getIntArray(TypedArray a, int index); + public Drawable getDrawable(TypedArray a, int index); + public CharSequence getText(TypedArray a, int index); + public int getResourceId(TypedArray a, int index, int defaultValue); + public int getInt(TypedArray a, int index, int defaultValue); + public int getFlag(TypedArray a, int index, int defaultValue); + public boolean getBoolean(TypedArray a, int index, boolean defaultValue); + } + + public static class EmptyKeyStyle implements KeyStyle { + private EmptyKeyStyle() { + } + + @Override + public int[] getIntArray(TypedArray a, int index) { + return parseIntArray(a, index); + } + + @Override + public Drawable getDrawable(TypedArray a, int index) { + return a.getDrawable(index); + } + + @Override + public CharSequence getText(TypedArray a, int index) { + return a.getText(index); + } + + @Override + public int getResourceId(TypedArray a, int index, int defaultValue) { + return a.getResourceId(index, defaultValue); + } + + @Override + public int getInt(TypedArray a, int index, int defaultValue) { + return a.getInt(index, defaultValue); + } + + @Override + public int getFlag(TypedArray a, int index, int defaultValue) { + return a.getInt(index, defaultValue); + } + + @Override + public boolean getBoolean(TypedArray a, int index, boolean defaultValue) { + return a.getBoolean(index, defaultValue); + } + + protected static int[] parseIntArray(TypedArray a, int index) { + TypedValue v = new TypedValue(); + a.getValue(index, v); + if (v.type == TypedValue.TYPE_INT_DEC || v.type == TypedValue.TYPE_INT_HEX) { + return new int[] { v.data }; + } else if (v.type == TypedValue.TYPE_STRING) { + return parseCSV(v.string.toString()); + } else { + return null; + } + } + + private static int[] parseCSV(String value) { + int count = 0; + int lastIndex = 0; + if (value.length() > 0) { + count++; + while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) { + count++; + } + } + int[] values = new int[count]; + count = 0; + StringTokenizer st = new StringTokenizer(value, ","); + while (st.hasMoreTokens()) { + try { + values[count++] = Integer.parseInt(st.nextToken()); + } catch (NumberFormatException nfe) { + Log.w(TAG, "Error parsing integer CSV " + value); + } + } + return values; + } + } + + public static class DeclaredKeyStyle extends EmptyKeyStyle { + private final HashMap mAttributes = new HashMap(); + + @Override + public int[] getIntArray(TypedArray a, int index) { + return a.hasValue(index) + ? super.getIntArray(a, index) : (int[])mAttributes.get(index); + } + + @Override + public Drawable getDrawable(TypedArray a, int index) { + return a.hasValue(index) + ? super.getDrawable(a, index) : (Drawable)mAttributes.get(index); + } + + @Override + public CharSequence getText(TypedArray a, int index) { + return a.hasValue(index) + ? super.getText(a, index) : (CharSequence)mAttributes.get(index); + } + + @Override + public int getResourceId(TypedArray a, int index, int defaultValue) { + final Integer value = (Integer)mAttributes.get(index); + return super.getResourceId(a, index, (value != null) ? value : defaultValue); + } + + @Override + public int getFlag(TypedArray a, int index, int defaultValue) { + final Integer value = (Integer)mAttributes.get(index); + return super.getFlag(a, index, defaultValue) | (value != null ? value : 0); + } + + @Override + public boolean getBoolean(TypedArray a, int index, boolean defaultValue) { + final Boolean value = (Boolean)mAttributes.get(index); + return super.getBoolean(a, index, (value != null) ? value : defaultValue); + } + + private DeclaredKeyStyle() { + super(); + } + + private void parseKeyStyleAttributes(TypedArray a) { + // TODO: Currently not all Key attributes can be declared as style. + readIntArray(a, R.styleable.Keyboard_Key_codes); + readText(a, R.styleable.Keyboard_Key_keyLabel); + readFlag(a, R.styleable.Keyboard_Key_keyLabelOption); + readText(a, R.styleable.Keyboard_Key_keyOutputText); + readDrawable(a, R.styleable.Keyboard_Key_keyIcon); + readDrawable(a, R.styleable.Keyboard_Key_iconPreview); + readDrawable(a, R.styleable.Keyboard_Key_keyHintIcon); + readDrawable(a, R.styleable.Keyboard_Key_shiftedIcon); + readResourceId(a, R.styleable.Keyboard_Key_popupKeyboard); + readBoolean(a, R.styleable.Keyboard_Key_isModifier); + readBoolean(a, R.styleable.Keyboard_Key_isSticky); + readBoolean(a, R.styleable.Keyboard_Key_isRepeatable); + } + + private void readDrawable(TypedArray a, int index) { + if (a.hasValue(index)) + mAttributes.put(index, a.getDrawable(index)); + } + + private void readText(TypedArray a, int index) { + if (a.hasValue(index)) + mAttributes.put(index, a.getText(index)); + } + + private void readResourceId(TypedArray a, int index) { + if (a.hasValue(index)) + mAttributes.put(index, a.getResourceId(index, 0)); + } + + private void readFlag(TypedArray a, int index) { + final Integer value = (Integer)mAttributes.get(index); + if (a.hasValue(index)) + mAttributes.put(index, a.getInt(index, 0) | (value != null ? value : 0)); + } + + private void readBoolean(TypedArray a, int index) { + if (a.hasValue(index)) + mAttributes.put(index, a.getBoolean(index, false)); + } + + private void readIntArray(TypedArray a, int index) { + if (a.hasValue(index)) { + final int[] value = parseIntArray(a, index); + if (value != null) + mAttributes.put(index, value); + } + } + + private void addParent(DeclaredKeyStyle parentStyle) { + mAttributes.putAll(parentStyle.mAttributes); + } + } + + public void parseKeyStyleAttributes(TypedArray a, TypedArray keyAttrs, + XmlResourceParser parser) { + String styleName = a.getString(R.styleable.Keyboard_KeyStyle_styleName); + if (mStyles.containsKey(styleName)) + throw new ParseException("duplicate key style declared: " + styleName, parser); + + final DeclaredKeyStyle style = new DeclaredKeyStyle(); + if (a.hasValue(R.styleable.Keyboard_KeyStyle_parentStyle)) { + String parentStyle = a.getString( + R.styleable.Keyboard_KeyStyle_parentStyle); + final DeclaredKeyStyle parent = mStyles.get(parentStyle); + if (parent == null) + throw new ParseException("Unknown parentStyle " + parent, parser); + style.addParent(parent); + } + style.parseKeyStyleAttributes(keyAttrs); + mStyles.put(styleName, style); + } + + public KeyStyle getKeyStyle(String styleName) { + return mStyles.get(styleName); + } + + public KeyStyle getEmptyKeyStyle() { + return EMPTY_KEY_STYLE; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java new file mode 100644 index 000000000..b75415579 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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 com.android.inputmethod.latin.R; + +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; +import android.util.Log; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard + * consists of rows of keys. + *

The layout file for a keyboard contains XML that looks like the following snippet:

+ *
+ * <Keyboard
+ *         latin:keyWidth="%10p"
+ *         latin:keyHeight="50px"
+ *         latin:horizontalGap="2px"
+ *         latin:verticalGap="2px" >
+ *     <Row latin:keyWidth="32px" >
+ *         <Key latin:keyLabel="A" />
+ *         ...
+ *     </Row>
+ *     ...
+ * </Keyboard>
+ * 
+ */ +public class Keyboard { + + static final String TAG = "Keyboard"; + + public static final int EDGE_LEFT = 0x01; + public static final int EDGE_RIGHT = 0x02; + public static final int EDGE_TOP = 0x04; + public static final int EDGE_BOTTOM = 0x08; + + public static final int KEYCODE_SHIFT = -1; + public static final int KEYCODE_MODE_CHANGE = -2; + public static final int KEYCODE_CANCEL = -3; + public static final int KEYCODE_DONE = -4; + public static final int KEYCODE_DELETE = -5; + public static final int KEYCODE_ALT = -6; + + /** Horizontal gap default for all rows */ + int mDefaultHorizontalGap; + + /** Default key width */ + int mDefaultWidth; + + /** Default key height */ + int mDefaultHeight; + + /** Default gap between rows */ + int mDefaultVerticalGap; + + /** Is the keyboard in the shifted state */ + private boolean mShifted; + + /** List of shift keys in this keyboard */ + private final List mShiftKeys = new ArrayList(); + + /** List of shift keys and its shifted state icon */ + private final HashMap mShiftedIcons = new HashMap(); + + /** Total height of the keyboard, including the padding and keys */ + private int mTotalHeight; + + /** + * Total width of the keyboard, including left side gaps and keys, but not any gaps on the + * right side. + */ + private int mTotalWidth; + + /** List of keys in this keyboard */ + private final List mKeys = new ArrayList(); + + /** Width of the screen available to fit the keyboard */ + final int mDisplayWidth; + + /** Height of the screen */ + final int mDisplayHeight; + + protected final KeyboardId mId; + + // Variables for pre-computing nearest keys. + + public final int GRID_WIDTH; + public final int GRID_HEIGHT; + private final int GRID_SIZE; + private int mCellWidth; + private int mCellHeight; + private int[][] mGridNeighbors; + private int mProximityThreshold; + private static int[] EMPTY_INT_ARRAY = new int[0]; + /** Number of key widths from current touch point to search for nearest keys. */ + private static float SEARCH_DISTANCE = 1.2f; + + /** + * Creates a keyboard from the given xml key layout file. + * @param context the application or service context + * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. + */ + public Keyboard(Context context, int xmlLayoutResId) { + this(context, xmlLayoutResId, null); + } + + /** + * Creates a keyboard from the given keyboard identifier. + * @param context the application or service context + * @param id keyboard identifier + */ + public Keyboard(Context context, KeyboardId id) { + this(context, id.getXmlId(), id); + } + + /** + * Creates a keyboard from the given xml key layout file. + * @param context the application or service context + * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. + * @param id keyboard identifier + */ + private Keyboard(Context context, int xmlLayoutResId, KeyboardId id) { + this(context, xmlLayoutResId, id, + context.getResources().getDisplayMetrics().widthPixels, + context.getResources().getDisplayMetrics().heightPixels); + } + + private Keyboard(Context context, int xmlLayoutResId, KeyboardId id, int width, + int height) { + Resources res = context.getResources(); + GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width); + GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height); + GRID_SIZE = GRID_WIDTH * GRID_HEIGHT; + + mDisplayWidth = width; + mDisplayHeight = height; + + mDefaultHorizontalGap = 0; + setKeyWidth(mDisplayWidth / 10); + mDefaultVerticalGap = 0; + mDefaultHeight = mDefaultWidth; + mId = id; + loadKeyboard(context, xmlLayoutResId); + } + + /** + *

Creates a blank keyboard from the given resource file and populates it with the specified + * characters in left-to-right, top-to-bottom fashion, using the specified number of columns. + *

+ *

If the specified number of columns is -1, then the keyboard will fit as many keys as + * possible in each row.

+ * @param context the application or service context + * @param layoutTemplateResId the layout template file, containing no keys. + * @param characters the list of characters to display on the keyboard. One key will be created + * for each character. + * @param columns the number of columns of keys to display. If this number is greater than the + * number of keys that can fit in a row, it will be ignored. If this number is -1, the + * keyboard will fit as many keys as possible in each row. + */ + public Keyboard(Context context, int layoutTemplateResId, + CharSequence characters, int columns, int horizontalPadding) { + this(context, layoutTemplateResId); + int x = 0; + int y = 0; + int column = 0; + mTotalWidth = 0; + + Row row = new Row(this); + row.defaultHeight = mDefaultHeight; + row.defaultWidth = mDefaultWidth; + row.defaultHorizontalGap = mDefaultHorizontalGap; + row.verticalGap = mDefaultVerticalGap; + row.rowEdgeFlags = EDGE_TOP | EDGE_BOTTOM; + final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns; + for (int i = 0; i < characters.length(); i++) { + char c = characters.charAt(i); + if (column >= maxColumns + || x + mDefaultWidth + horizontalPadding > mDisplayWidth) { + x = 0; + y += mDefaultVerticalGap + mDefaultHeight; + column = 0; + } + final Key key = new Key(row); + // Horizontal gap is divided equally to both sides of the key. + key.x = x + key.gap / 2; + key.y = y; + key.label = String.valueOf(c); + key.codes = new int[] { c }; + column++; + x += key.width + key.gap; + mKeys.add(key); + if (x > mTotalWidth) { + mTotalWidth = x; + } + } + mTotalHeight = y + mDefaultHeight; + } + + public KeyboardId getKeyboardId() { + return mId; + } + + public List getKeys() { + return mKeys; + } + + protected int getHorizontalGap() { + return mDefaultHorizontalGap; + } + + protected void setHorizontalGap(int gap) { + mDefaultHorizontalGap = gap; + } + + protected int getVerticalGap() { + return mDefaultVerticalGap; + } + + protected void setVerticalGap(int gap) { + mDefaultVerticalGap = gap; + } + + protected int getKeyHeight() { + return mDefaultHeight; + } + + protected void setKeyHeight(int height) { + mDefaultHeight = height; + } + + protected int getKeyWidth() { + return mDefaultWidth; + } + + protected void setKeyWidth(int width) { + mDefaultWidth = width; + final int threshold = (int) (width * SEARCH_DISTANCE); + mProximityThreshold = threshold * threshold; + } + + /** + * Returns the total height of the keyboard + * @return the total height of the keyboard + */ + public int getHeight() { + return mTotalHeight; + } + + public int getMinWidth() { + return mTotalWidth; + } + + public int getKeyboardHeight() { + return mDisplayHeight; + } + + public int getKeyboardWidth() { + return mDisplayWidth; + } + + public boolean setShifted(boolean shiftState) { + for (final Key key : mShiftKeys) { + key.on = shiftState; + } + if (mShifted != shiftState) { + mShifted = shiftState; + return true; + } + return false; + } + + public boolean isShiftedOrShiftLocked() { + return mShifted; + } + + public List getShiftKeys() { + return mShiftKeys; + } + + public Map getShiftedIcons() { + return mShiftedIcons; + } + + private void computeNearestNeighbors() { + // Round-up so we don't have any pixels outside the grid + mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH; + mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT; + mGridNeighbors = new int[GRID_SIZE][]; + final int[] indices = new int[mKeys.size()]; + final int gridWidth = GRID_WIDTH * mCellWidth; + final int gridHeight = GRID_HEIGHT * mCellHeight; + final int threshold = mProximityThreshold; + for (int x = 0; x < gridWidth; x += mCellWidth) { + for (int y = 0; y < gridHeight; y += mCellHeight) { + final int centerX = x + mCellWidth / 2; + final int centerY = y + mCellHeight / 2; + int count = 0; + for (int i = 0; i < mKeys.size(); i++) { + final Key key = mKeys.get(i); + if (key.squaredDistanceToEdge(centerX, centerY) < threshold) + indices[count++] = i; + } + final int[] cell = new int[count]; + System.arraycopy(indices, 0, cell, 0, count); + mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell; + } + } + } + + /** + * Returns the indices of the keys that are closest to the given point. + * @param x the x-coordinate of the point + * @param y the y-coordinate of the point + * @return the array of integer indices for the nearest keys to the given point. If the given + * point is out of range, then an array of size zero is returned. + */ + public int[] getNearestKeys(int x, int y) { + if (mGridNeighbors == null) computeNearestNeighbors(); + if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) { + int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth); + if (index < GRID_SIZE) { + return mGridNeighbors[index]; + } + } + return EMPTY_INT_ARRAY; + } + + // TODO should be private + protected Row createRowFromXml(Resources res, XmlResourceParser parser) { + return new Row(res, this, parser); + } + + // TODO should be private + protected Key createKeyFromXml(Resources res, Row parent, int x, int y, + XmlResourceParser parser, KeyStyles keyStyles) { + return new Key(res, parent, x, y, parser, keyStyles); + } + + private void loadKeyboard(Context context, int xmlLayoutResId) { + try { + final Resources res = context.getResources(); + KeyboardParser parser = new KeyboardParser(this, res); + parser.parseKeyboard(res.getXml(xmlLayoutResId)); + // mTotalWidth is the width of this keyboard which is maximum width of row. + mTotalWidth = parser.getMaxRowWidth(); + mTotalHeight = parser.getTotalHeight(); + } catch (XmlPullParserException e) { + Log.w(TAG, "keyboard XML parse error: " + e); + throw new IllegalArgumentException(e); + } catch (IOException e) { + Log.w(TAG, "keyboard XML parse error: " + e); + throw new RuntimeException(e); + } + } + + protected static void setDefaultBounds(Drawable drawable) { + if (drawable != null) + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight()); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java new file mode 100644 index 000000000..e52db2957 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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; + +public interface KeyboardActionListener { + + /** + * Called when the user presses a key. This is sent before the + * {@link #onKey} is called. For keys that repeat, this is only + * called once. + * + * @param primaryCode + * the unicode of the key being pressed. If the touch is + * not on a valid key, the value will be zero. + */ + void onPress(int primaryCode); + + /** + * Called when the user releases a key. This is sent after the + * {@link #onKey} is called. For keys that repeat, this is only + * called once. + * + * @param primaryCode + * the code of the key that was released + */ + void onRelease(int primaryCode); + + /** + * Send a key press to the listener. + * + * @param primaryCode + * this is the key that was pressed + * @param keyCodes + * the codes for all the possible alternative keys with + * the primary code being the first. If the primary key + * code is a single character such as an alphabet or + * number or symbol, the alternatives will include other + * characters that may be on the same key or adjacent + * keys. These codes are useful to correct for + * accidental presses of a key adjacent to the intended + * key. + * @param x + * x-coordinate pixel of touched event. If onKey is not called by onTouchEvent, + * the value should be NOT_A_TOUCH_COORDINATE. + * @param y + * y-coordinate pixel of touched event. If onKey is not called by onTouchEvent, + * the value should be NOT_A_TOUCH_COORDINATE. + */ + void onKey(int primaryCode, int[] keyCodes, int x, int y); + + /** + * Sends a sequence of characters to the listener. + * + * @param text + * the sequence of characters to be displayed. + */ + void onText(CharSequence text); + + /** + * Called when user released a finger outside any key. + */ + void onCancel(); + + /** + * Called when the user quickly moves the finger from right to + * left. + */ + void swipeLeft(); + + /** + * Called when the user quickly moves the finger from left to + * right. + */ + void swipeRight(); + + /** + * Called when the user quickly moves the finger from up to down. + */ + void swipeDown(); + + /** + * Called when the user quickly moves the finger from down to up. + */ + void swipeUp(); +} diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java new file mode 100644 index 000000000..289e4c0e8 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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 com.android.inputmethod.latin.R; + +import android.view.inputmethod.EditorInfo; + +import java.util.Arrays; +import java.util.Locale; + +/** + * Represents the parameters necessary to construct a new LatinKeyboard, + * which also serve as a unique identifier for each keyboard type. + */ +public class KeyboardId { + public static final int MODE_TEXT = 0; + public static final int MODE_URL = 1; + public static final int MODE_EMAIL = 2; + public static final int MODE_IM = 3; + public static final int MODE_WEB = 4; + public static final int MODE_PHONE = 5; + public static final int MODE_NUMBER = 6; + + public final Locale mLocale; + public final int mOrientation; + public final int mMode; + public final int mXmlId; + public final int mColorScheme; + public final boolean mHasSettingsKey; + public final boolean mVoiceKeyEnabled; + public final boolean mHasVoiceKey; + public final int mImeOptions; + public final boolean mEnableShiftLock; + + private final int mHashCode; + + public KeyboardId(Locale locale, int orientation, int mode, + int xmlId, int colorScheme, boolean hasSettingsKey, boolean voiceKeyEnabled, + boolean hasVoiceKey, int imeOptions, boolean enableShiftLock) { + this.mLocale = locale; + this.mOrientation = orientation; + this.mMode = mode; + this.mXmlId = xmlId; + this.mColorScheme = colorScheme; + this.mHasSettingsKey = hasSettingsKey; + this.mVoiceKeyEnabled = voiceKeyEnabled; + this.mHasVoiceKey = hasVoiceKey; + // We are interested only in IME_MASK_ACTION enum value and IME_FLAG_NO_ENTER_ACTION. + this.mImeOptions = imeOptions + & (EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION); + this.mEnableShiftLock = enableShiftLock; + + this.mHashCode = Arrays.hashCode(new Object[] { + locale, + orientation, + mode, + xmlId, + colorScheme, + hasSettingsKey, + voiceKeyEnabled, + hasVoiceKey, + imeOptions, + enableShiftLock, + }); + } + + public int getXmlId() { + return mXmlId; + } + + public boolean isAlphabetMode() { + return mXmlId == R.xml.kbd_qwerty; + } + + @Override + public boolean equals(Object other) { + return other instanceof KeyboardId && equals((KeyboardId) other); + } + + boolean equals(KeyboardId other) { + return other.mLocale.equals(this.mLocale) + && other.mOrientation == this.mOrientation + && other.mMode == this.mMode + && other.mXmlId == this.mXmlId + && other.mColorScheme == this.mColorScheme + && other.mHasSettingsKey == this.mHasSettingsKey + && other.mVoiceKeyEnabled == this.mVoiceKeyEnabled + && other.mHasVoiceKey == this.mHasVoiceKey + && other.mImeOptions == this.mImeOptions + && other.mEnableShiftLock == this.mEnableShiftLock; + } + + @Override + public int hashCode() { + return mHashCode; + } + + @Override + public String toString() { + return String.format("[%s %s %5s imeOptions=0x%08x xml=0x%08x %s%s%s%s%s]", + mLocale, + (mOrientation == 1 ? "port" : "land"), + modeName(mMode), + mImeOptions, + mXmlId, + colorSchemeName(mColorScheme), + (mHasSettingsKey ? " hasSettingsKey" : ""), + (mVoiceKeyEnabled ? " voiceKeyEnabled" : ""), + (mHasVoiceKey ? " hasVoiceKey" : ""), + (mEnableShiftLock ? " enableShiftLock" : "")); + } + + private static String modeName(int mode) { + switch (mode) { + case MODE_TEXT: return "text"; + case MODE_URL: return "url"; + case MODE_EMAIL: return "email"; + case MODE_IM: return "im"; + case MODE_WEB: return "web"; + case MODE_PHONE: return "phone"; + case MODE_NUMBER: return "number"; + } + return null; + } + + private static String colorSchemeName(int colorScheme) { + switch (colorScheme) { + case KeyboardView.COLOR_SCHEME_WHITE: return "white"; + case KeyboardView.COLOR_SCHEME_BLACK: return "black"; + } + return null; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java new file mode 100644 index 000000000..2147ee289 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java @@ -0,0 +1,569 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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 com.android.inputmethod.latin.R; + +import org.xmlpull.v1.XmlPullParserException; + +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.util.Log; +import android.util.TypedValue; +import android.util.Xml; +import android.view.InflateException; + +import java.io.IOException; +import java.util.List; + +/** + * Parser for BaseKeyboard. + * + * This class parses Keyboard XML file and fill out keys in Keyboard. + * The Keyboard XML file looks like: + *
+ *   >!-- xml/keyboard.xml --<
+ *   >Keyboard keyboard_attributes*<
+ *     >!-- Keyboard Content --<
+ *     >Row row_attributes*<
+ *       >!-- Row Content --<
+ *       >Key key_attributes* /<
+ *       >Spacer horizontalGap="0.2in" /<
+ *       >include keyboardLayout="@xml/other_keys"<
+ *       ...
+ *     >/Row<
+ *     >include keyboardLayout="@xml/other_rows"<
+ *     ...
+ *   >/Keyboard<
+ * 
+ * The XML file which is included in other file must have >merge< as root element, such as: + *
+ *   >!-- xml/other_keys.xml --<
+ *   >merge<
+ *     >Key key_attributes* /<
+ *     ...
+ *   >/merge<
+ * 
+ * and + *
+ *   >!-- xml/other_rows.xml --<
+ *   >merge<
+ *     >Row row_attributes*<
+ *       >Key key_attributes* /<
+ *     >/Row<
+ *     ...
+ *   >/merge<
+ * 
+ * You can also use switch-case-default tags to select Rows and Keys. + *
+ *   >switch<
+ *     >case case_attribute*<
+ *       >!-- Any valid tags at switch position --<
+ *     >/case<
+ *     ...
+ *     >default<
+ *       >!-- Any valid tags at switch position --<
+ *     >/default<
+ *   >/switch<
+ * 
+ * You can declare Key style and specify styles within Key tags. + *
+ *     >switch<
+ *       >case colorScheme="white"<
+ *         >key-style styleName="shift-key" parentStyle="modifier-key"
+ *           keyIcon="@drawable/sym_keyboard_shift"
+ *         /<
+ *       >/case<
+ *       >case colorScheme="black"<
+ *         >key-style styleName="shift-key" parentStyle="modifier-key"
+ *           keyIcon="@drawable/sym_bkeyboard_shift"
+ *         /<
+ *       >/case<
+ *     >/switch<
+ *     ...
+ *     >Key keyStyle="shift-key" ... /<
+ * 
+ */ + +public class KeyboardParser { + private static final String TAG = "KeyboardParser"; + private static final boolean DEBUG_TAG = false; + + // Keyboard XML Tags + private static final String TAG_KEYBOARD = "Keyboard"; + private static final String TAG_ROW = "Row"; + private static final String TAG_KEY = "Key"; + private static final String TAG_SPACER = "Spacer"; + private static final String TAG_INCLUDE = "include"; + private static final String TAG_MERGE = "merge"; + private static final String TAG_SWITCH = "switch"; + private static final String TAG_CASE = "case"; + private static final String TAG_DEFAULT = "default"; + private static final String TAG_KEY_STYLE = "key-style"; + + private final Keyboard mKeyboard; + private final Resources mResources; + + private int mCurrentX = 0; + private int mCurrentY = 0; + private int mMaxRowWidth = 0; + private int mTotalHeight = 0; + private Row mCurrentRow = null; + private final KeyStyles mKeyStyles = new KeyStyles(); + + public KeyboardParser(Keyboard keyboard, Resources res) { + mKeyboard = keyboard; + mResources = res; + } + + public int getMaxRowWidth() { + return mMaxRowWidth; + } + + public int getTotalHeight() { + return mTotalHeight; + } + + public void parseKeyboard(XmlResourceParser parser) + throws XmlPullParserException, IOException { + int event; + while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { + if (event == XmlResourceParser.START_TAG) { + final String tag = parser.getName(); + if (DEBUG_TAG) debugStartTag("parseKeyboard", tag, false); + if (TAG_KEYBOARD.equals(tag)) { + parseKeyboardAttributes(parser); + parseKeyboardContent(parser, mKeyboard.getKeys()); + break; + } else { + throw new IllegalStartTag(parser, TAG_KEYBOARD); + } + } + } + } + + private void parseKeyboardAttributes(XmlResourceParser parser) { + final Keyboard keyboard = mKeyboard; + final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard); + final int width = keyboard.getKeyboardWidth(); + final int height = keyboard.getKeyboardHeight(); + keyboard.setKeyWidth(getDimensionOrFraction(a, + R.styleable.Keyboard_keyWidth, width, width / 10)); + keyboard.setKeyHeight(getDimensionOrFraction(a, + R.styleable.Keyboard_keyHeight, height, 50)); + keyboard.setHorizontalGap(getDimensionOrFraction(a, + R.styleable.Keyboard_horizontalGap, width, 0)); + keyboard.setVerticalGap(getDimensionOrFraction(a, + R.styleable.Keyboard_verticalGap, height, 0)); + a.recycle(); + if (DEBUG_TAG) Log.d(TAG, "id=" + keyboard.mId); + } + + private void parseKeyboardContent(XmlResourceParser parser, List keys) + throws XmlPullParserException, IOException { + int event; + while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { + if (event == XmlResourceParser.START_TAG) { + final String tag = parser.getName(); + if (DEBUG_TAG) debugStartTag("parseKeyboardContent", tag, keys == null); + if (TAG_ROW.equals(tag)) { + Row row = mKeyboard.createRowFromXml(mResources, parser); + if (keys != null) + startRow(row); + parseRowContent(parser, row, keys); + } else if (TAG_INCLUDE.equals(tag)) { + parseIncludeKeyboardContent(parser, keys); + } else if (TAG_SWITCH.equals(tag)) { + parseSwitchKeyboardContent(parser, keys); + } else if (TAG_KEY_STYLE.equals(tag)) { + parseKeyStyle(parser, keys); + } else { + throw new IllegalStartTag(parser, TAG_ROW); + } + } else if (event == XmlResourceParser.END_TAG) { + final String tag = parser.getName(); + if (DEBUG_TAG) debugEndTag("parseKeyboardContent", tag, keys == null); + if (TAG_KEYBOARD.equals(tag)) { + endKeyboard(mKeyboard.getVerticalGap()); + break; + } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)) { + break; + } else if (TAG_MERGE.equals(tag)) { + break; + } else if (TAG_KEY_STYLE.equals(tag)) { + continue; + } else { + throw new IllegalEndTag(parser, TAG_ROW); + } + } + } + } + + private void parseRowContent(XmlResourceParser parser, Row row, List keys) + throws XmlPullParserException, IOException { + int event; + while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { + if (event == XmlResourceParser.START_TAG) { + final String tag = parser.getName(); + if (DEBUG_TAG) debugStartTag("parseRowContent", tag, keys == null); + if (TAG_KEY.equals(tag)) { + parseKey(parser, row, keys); + } else if (TAG_SPACER.equals(tag)) { + parseSpacer(parser, keys); + } else if (TAG_INCLUDE.equals(tag)) { + parseIncludeRowContent(parser, row, keys); + } else if (TAG_SWITCH.equals(tag)) { + parseSwitchRowContent(parser, row, keys); + } else if (TAG_KEY_STYLE.equals(tag)) { + parseKeyStyle(parser, keys); + } else { + throw new IllegalStartTag(parser, TAG_KEY); + } + } else if (event == XmlResourceParser.END_TAG) { + final String tag = parser.getName(); + if (DEBUG_TAG) debugEndTag("parseRowContent", tag, keys == null); + if (TAG_ROW.equals(tag)) { + if (keys != null) + endRow(); + break; + } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)) { + break; + } else if (TAG_MERGE.equals(tag)) { + break; + } else if (TAG_KEY_STYLE.equals(tag)) { + continue; + } else { + throw new IllegalEndTag(parser, TAG_KEY); + } + } + } + } + + private void parseKey(XmlResourceParser parser, Row row, List keys) + throws XmlPullParserException, IOException { + if (keys == null) { + checkEndTag(TAG_KEY, parser); + } else { + Key key = mKeyboard.createKeyFromXml(mResources, row, mCurrentX, mCurrentY, parser, + mKeyStyles); + checkEndTag(TAG_KEY, parser); + keys.add(key); + if (key.codes[0] == Keyboard.KEYCODE_SHIFT) + mKeyboard.getShiftKeys().add(key); + endKey(key); + } + } + + private void parseSpacer(XmlResourceParser parser, List keys) + throws XmlPullParserException, IOException { + if (keys == null) { + checkEndTag(TAG_SPACER, parser); + } else { + final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard); + final int gap = getDimensionOrFraction(a, R.styleable.Keyboard_horizontalGap, + mKeyboard.getKeyboardWidth(), 0); + a.recycle(); + checkEndTag(TAG_SPACER, parser); + setSpacer(gap); + } + } + + private void parseIncludeKeyboardContent(XmlResourceParser parser, List keys) + throws XmlPullParserException, IOException { + parseIncludeInternal(parser, null, keys); + } + + private void parseIncludeRowContent(XmlResourceParser parser, Row row, List keys) + throws XmlPullParserException, IOException { + parseIncludeInternal(parser, row, keys); + } + + private void parseIncludeInternal(XmlResourceParser parser, Row row, List keys) + throws XmlPullParserException, IOException { + if (keys == null) { + checkEndTag(TAG_INCLUDE, parser); + } else { + final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard_Include); + final int keyboardLayout = a.getResourceId( + R.styleable.Keyboard_Include_keyboardLayout, 0); + a.recycle(); + + checkEndTag(TAG_INCLUDE, parser); + if (keyboardLayout == 0) + throw new ParseException("No keyboardLayout attribute in ", parser); + if (DEBUG_TAG) Log.d(TAG, String.format(" keyboardLayout=0x%08x", keyboardLayout)); + parseMerge(mResources.getLayout(keyboardLayout), row, keys); + } + } + + private void parseMerge(XmlResourceParser parser, Row row, List keys) + throws XmlPullParserException, IOException { + int event; + while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { + if (event == XmlResourceParser.START_TAG) { + final String tag = parser.getName(); + if (DEBUG_TAG) debugStartTag("parseMerge", tag, keys == null); + if (TAG_MERGE.equals(tag)) { + if (row == null) { + parseKeyboardContent(parser, keys); + } else { + parseRowContent(parser, row, keys); + } + break; + } else { + throw new ParseException( + "Included keyboard layout must have root element", parser); + } + } + } + } + + private void parseSwitchKeyboardContent(XmlResourceParser parser, List keys) + throws XmlPullParserException, IOException { + parseSwitchInternal(parser, null, keys); + } + + private void parseSwitchRowContent(XmlResourceParser parser, Row row, List keys) + throws XmlPullParserException, IOException { + parseSwitchInternal(parser, row, keys); + } + + private void parseSwitchInternal(XmlResourceParser parser, Row row, List keys) + throws XmlPullParserException, IOException { + boolean selected = false; + int event; + if (DEBUG_TAG) Log.d(TAG, "parseSwitchInternal: id=" + mKeyboard.mId); + while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { + if (event == XmlResourceParser.START_TAG) { + final String tag = parser.getName(); + if (DEBUG_TAG) debugStartTag("parseSwitchInternal", tag, keys == null); + if (TAG_CASE.equals(tag)) { + selected |= parseCase(parser, row, selected ? null : keys); + } else if (TAG_DEFAULT.equals(tag)) { + selected |= parseDefault(parser, row, selected ? null : keys); + } else { + throw new IllegalStartTag(parser, TAG_KEY); + } + } else if (event == XmlResourceParser.END_TAG) { + final String tag = parser.getName(); + if (DEBUG_TAG) debugEndTag("parseRowContent", tag, keys == null); + if (TAG_SWITCH.equals(tag)) { + break; + } else { + throw new IllegalEndTag(parser, TAG_KEY); + } + } + } + } + + private boolean parseCase(XmlResourceParser parser, Row row, List keys) + throws XmlPullParserException, IOException { + final boolean selected = parseCaseCondition(parser); + if (row == null) { + // Processing Rows. + parseKeyboardContent(parser, selected ? keys : null); + } else { + // Processing Keys. + parseRowContent(parser, row, selected ? keys : null); + } + return selected; + } + + private boolean parseCaseCondition(XmlResourceParser parser) { + final KeyboardId id = mKeyboard.mId; + if (id == null) + return true; + + final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard_Case); + final TypedArray viewAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.KeyboardView); + try { + final boolean modeMatched = matchInteger(a, + R.styleable.Keyboard_Case_mode, id.mMode); + final boolean settingsKeyMatched = matchBoolean(a, + R.styleable.Keyboard_Case_hasSettingsKey, id.mHasSettingsKey); + final boolean voiceEnabledMatched = matchBoolean(a, + R.styleable.Keyboard_Case_voiceKeyEnabled, id.mVoiceKeyEnabled); + final boolean voiceKeyMatched = matchBoolean(a, + R.styleable.Keyboard_Case_hasVoiceKey, id.mHasVoiceKey); + final boolean colorSchemeMatched = matchInteger(viewAttr, + R.styleable.KeyboardView_colorScheme, id.mColorScheme); + // As noted at KeyboardSwitcher.KeyboardId class, we are interested only in + // enum value masked by IME_MASK_ACTION and IME_FLAG_NO_ENTER_ACTION. So matching + // this attribute with id.mImeOptions as integer value is enough for our purpose. + final boolean imeOptionsMatched = matchInteger(a, + R.styleable.Keyboard_Case_imeOptions, id.mImeOptions); + final boolean selected = modeMatched && settingsKeyMatched && voiceEnabledMatched + && voiceKeyMatched && colorSchemeMatched && imeOptionsMatched; + + if (DEBUG_TAG) { + Log.d(TAG, String.format( + "parseCaseCondition: %s%s%s%s%s%s%s", + Boolean.toString(selected).toUpperCase(), + debugInteger(a, R.styleable.Keyboard_Case_mode, "mode"), + debugBoolean(a, R.styleable.Keyboard_Case_hasSettingsKey, + "hasSettingsKey"), + debugBoolean(a, R.styleable.Keyboard_Case_voiceKeyEnabled, + "voiceKeyEnabled"), + debugBoolean(a, R.styleable.Keyboard_Case_hasVoiceKey, "hasVoiceKey"), + debugInteger(viewAttr, R.styleable.KeyboardView_colorScheme, + "colorScheme"), + debugInteger(a, R.styleable.Keyboard_Case_imeOptions, "imeOptions"))); + } + + return selected; + } finally { + a.recycle(); + viewAttr.recycle(); + } + } + + private static boolean matchInteger(TypedArray a, int index, int value) { + // If does not have "index" attribute, that means this is wild-card for the + // attribute. + return !a.hasValue(index) || a.getInt(index, 0) == value; + } + + private static boolean matchBoolean(TypedArray a, int index, boolean value) { + // If does not have "index" attribute, that means this is wild-card for the + // attribute. + return !a.hasValue(index) || a.getBoolean(index, false) == value; + } + + private boolean parseDefault(XmlResourceParser parser, Row row, List keys) + throws XmlPullParserException, IOException { + if (row == null) { + parseKeyboardContent(parser, keys); + } else { + parseRowContent(parser, row, keys); + } + return true; + } + + private void parseKeyStyle(XmlResourceParser parser, List keys) + throws XmlPullParserException, IOException { + TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard_KeyStyle); + TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard_Key); + try { + if (!a.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) + throw new ParseException("<" + TAG_KEY_STYLE + + "/> needs styleName attribute", parser); + if (keys != null) + mKeyStyles.parseKeyStyleAttributes(a, keyAttrs, parser); + } finally { + a.recycle(); + keyAttrs.recycle(); + } + } + + private static void checkEndTag(String tag, XmlResourceParser parser) + throws XmlPullParserException, IOException { + if (parser.next() == XmlResourceParser.END_TAG && tag.equals(parser.getName())) + return; + throw new NonEmptyTag(tag, parser); + } + + private void startRow(Row row) { + mCurrentX = 0; + mCurrentRow = row; + } + + private void endRow() { + if (mCurrentRow == null) + throw new InflateException("orphant end row tag"); + mCurrentY += mCurrentRow.verticalGap + mCurrentRow.defaultHeight; + mCurrentRow = null; + } + + private void endKey(Key key) { + mCurrentX += key.gap + key.width; + if (mCurrentX > mMaxRowWidth) + mMaxRowWidth = mCurrentX; + } + + private void endKeyboard(int defaultVerticalGap) { + mTotalHeight = mCurrentY - defaultVerticalGap; + } + + private void setSpacer(int gap) { + mCurrentX += gap; + } + + public static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) { + final TypedValue value = a.peekValue(index); + if (value == null) + return defValue; + if (value.type == TypedValue.TYPE_DIMENSION) { + return a.getDimensionPixelOffset(index, defValue); + } else if (value.type == TypedValue.TYPE_FRACTION) { + // Round it to avoid values like 47.9999 from getting truncated + return Math.round(a.getFraction(index, base, base, defValue)); + } + return defValue; + } + + @SuppressWarnings("serial") + public static class ParseException extends InflateException { + public ParseException(String msg, XmlResourceParser parser) { + super(msg + " at line " + parser.getLineNumber()); + } + } + + @SuppressWarnings("serial") + private static class IllegalStartTag extends ParseException { + public IllegalStartTag(XmlResourceParser parser, String parent) { + super("Illegal start tag " + parser.getName() + " in " + parent, parser); + } + } + + @SuppressWarnings("serial") + private static class IllegalEndTag extends ParseException { + public IllegalEndTag(XmlResourceParser parser, String parent) { + super("Illegal end tag " + parser.getName() + " in " + parent, parser); + } + } + + @SuppressWarnings("serial") + private static class NonEmptyTag extends ParseException { + public NonEmptyTag(String tag, XmlResourceParser parser) { + super(tag + " must be empty tag", parser); + } + } + + private static void debugStartTag(String title, String tag, boolean skip) { + Log.d(TAG, title + ": <" + tag + ">" + (skip ? " skip" : "")); + } + + private static void debugEndTag(String title, String tag, boolean skip) { + Log.d(TAG, title + ": " + (skip ? " skip" : "")); + } + + private static String debugInteger(TypedArray a, int index, String name) { + return a.hasValue(index) ? " " + name + "=" + a.getInt(index, 0) : ""; + } + + private static String debugBoolean(TypedArray a, int index, String name) { + return a.hasValue(index) ? " " + name + "=" + a.getBoolean(index, false) : ""; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java new file mode 100644 index 000000000..ae6d09716 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -0,0 +1,1469 @@ +/* + * Copyright (C) 2010 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 com.android.inputmethod.latin.LatinImeLogger; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.SubtypeSwitcher; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.Region.Op; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.view.WindowManager; +import android.widget.PopupWindow; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.WeakHashMap; + +/** + * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and detecting key + * presses and touch movements. + * + * TODO: References to LatinKeyboard in this class should be replaced with ones to its base class. + * + * @attr ref R.styleable#KeyboardView_keyBackground + * @attr ref R.styleable#KeyboardView_keyPreviewLayout + * @attr ref R.styleable#KeyboardView_keyPreviewOffset + * @attr ref R.styleable#KeyboardView_labelTextSize + * @attr ref R.styleable#KeyboardView_keyTextSize + * @attr ref R.styleable#KeyboardView_keyTextColor + * @attr ref R.styleable#KeyboardView_verticalCorrection + * @attr ref R.styleable#KeyboardView_popupLayout + */ +public class KeyboardView extends View implements PointerTracker.UIProxy { + private static final String TAG = "KeyboardView"; + private static final boolean DEBUG = false; + private static final boolean DEBUG_SHOW_ALIGN = false; + private static final boolean DEBUG_KEYBOARD_GRID = false; + + public static final int COLOR_SCHEME_WHITE = 0; + public static final int COLOR_SCHEME_BLACK = 1; + + public static final int NOT_A_TOUCH_COORDINATE = -1; + + // Timing constants + private final int mKeyRepeatInterval; + + // Miscellaneous constants + private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable }; + private static final int HINT_ICON_VERTICAL_ADJUSTMENT_PIXEL = -1; + + // XML attribute + private int mKeyLetterSize; + private int mKeyTextColor; + private int mKeyTextColorDisabled; + private Typeface mKeyLetterStyle = Typeface.DEFAULT; + private int mLabelTextSize; + private int mColorScheme = COLOR_SCHEME_WHITE; + private int mShadowColor; + private float mShadowRadius; + private Drawable mKeyBackground; + private float mBackgroundDimAmount; + private float mKeyHysteresisDistance; + private float mVerticalCorrection; + private int mPreviewOffset; + private int mPreviewHeight; + private int mPopupLayout; + + // Main keyboard + private Keyboard mKeyboard; + private Key[] mKeys; + + // Key preview popup + private boolean mInForeground; + private TextView mPreviewText; + private PopupWindow mPreviewPopup; + private int mPreviewTextSizeLarge; + private int[] mOffsetInWindow; + private int mOldPreviewKeyIndex = KeyDetector.NOT_A_KEY; + private boolean mShowPreview = true; + private boolean mShowTouchPoints = true; + private int mPopupPreviewOffsetX; + private int mPopupPreviewOffsetY; + private int mWindowY; + private int mPopupPreviewDisplayedY; + private final int mDelayBeforePreview; + private final int mDelayAfterPreview; + + // Popup mini keyboard + private PopupWindow mMiniKeyboardPopup; + private KeyboardView mMiniKeyboard; + private View mMiniKeyboardParent; + private final WeakHashMap mMiniKeyboardCache = new WeakHashMap(); + private int mMiniKeyboardOriginX; + private int mMiniKeyboardOriginY; + private long mMiniKeyboardPopupTime; + private int[] mWindowOffset; + private final float mMiniKeyboardSlideAllowance; + private int mMiniKeyboardTrackerId; + + /** Listener for {@link KeyboardActionListener}. */ + private KeyboardActionListener mKeyboardActionListener; + + private final ArrayList mPointerTrackers = new ArrayList(); + + // TODO: Let the PointerTracker class manage this pointer queue + private final PointerTrackerQueue mPointerQueue = new PointerTrackerQueue(); + + private final boolean mHasDistinctMultitouch; + private int mOldPointerCount = 1; + + protected KeyDetector mKeyDetector = new ProximityKeyDetector(); + + // Swipe gesture detector + private GestureDetector mGestureDetector; + private final SwipeTracker mSwipeTracker = new SwipeTracker(); + private final int mSwipeThreshold; + private final boolean mDisambiguateSwipe; + + // Drawing + /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/ + private boolean mDrawPending; + /** The dirty region in the keyboard bitmap */ + private final Rect mDirtyRect = new Rect(); + /** The keyboard bitmap for faster updates */ + private Bitmap mBuffer; + /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */ + private boolean mKeyboardChanged; + private Key mInvalidatedKey; + /** The canvas for the above mutable keyboard bitmap */ + private Canvas mCanvas; + private final Paint mPaint; + private final Rect mPadding; + private final Rect mClipRegion = new Rect(0, 0, 0, 0); + // This map caches key label text height in pixel as value and key label text size as map key. + private final HashMap mTextHeightCache = new HashMap(); + // Distance from horizontal center of the key, proportional to key label text height and width. + private final float KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR_CENTER = 0.55f; + private final float KEY_LABEL_VERTICAL_PADDING_FACTOR = 1.60f; + private final String KEY_LABEL_REFERENCE_CHAR = "H"; + private final int KEY_LABEL_OPTION_ALIGN_LEFT = 1; + private final int KEY_LABEL_OPTION_ALIGN_RIGHT = 2; + private final int KEY_LABEL_OPTION_ALIGN_BOTTOM = 8; + private final int KEY_LABEL_OPTION_FONT_NORMAL = 16; + private final int mKeyLabelHorizontalPadding; + + private final UIHandler mHandler = new UIHandler(); + + class UIHandler extends Handler { + private static final int MSG_POPUP_PREVIEW = 1; + private static final int MSG_DISMISS_PREVIEW = 2; + private static final int MSG_REPEAT_KEY = 3; + private static final int MSG_LONGPRESS_KEY = 4; + private static final int MSG_LONGPRESS_SHIFT_KEY = 5; + + private boolean mInKeyRepeat; + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_POPUP_PREVIEW: + showKey(msg.arg1, (PointerTracker)msg.obj); + break; + case MSG_DISMISS_PREVIEW: + mPreviewPopup.dismiss(); + break; + case MSG_REPEAT_KEY: { + final PointerTracker tracker = (PointerTracker)msg.obj; + tracker.repeatKey(msg.arg1); + startKeyRepeatTimer(mKeyRepeatInterval, msg.arg1, tracker); + break; + } + case MSG_LONGPRESS_KEY: { + final PointerTracker tracker = (PointerTracker)msg.obj; + openPopupIfRequired(msg.arg1, tracker); + break; + } + case MSG_LONGPRESS_SHIFT_KEY: { + final PointerTracker tracker = (PointerTracker)msg.obj; + onLongPressShiftKey(tracker); + break; + } + } + } + + public void popupPreview(long delay, int keyIndex, PointerTracker tracker) { + removeMessages(MSG_POPUP_PREVIEW); + if (mPreviewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) { + // Show right away, if it's already visible and finger is moving around + showKey(keyIndex, tracker); + } else { + sendMessageDelayed(obtainMessage(MSG_POPUP_PREVIEW, keyIndex, 0, tracker), + delay); + } + } + + public void cancelPopupPreview() { + removeMessages(MSG_POPUP_PREVIEW); + } + + public void dismissPreview(long delay) { + if (mPreviewPopup.isShowing()) { + sendMessageDelayed(obtainMessage(MSG_DISMISS_PREVIEW), delay); + } + } + + public void cancelDismissPreview() { + removeMessages(MSG_DISMISS_PREVIEW); + } + + public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) { + mInKeyRepeat = true; + sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay); + } + + public void cancelKeyRepeatTimer() { + mInKeyRepeat = false; + removeMessages(MSG_REPEAT_KEY); + } + + public boolean isInKeyRepeat() { + return mInKeyRepeat; + } + + public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) { + cancelLongPressTimers(); + sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay); + } + + public void startLongPressShiftTimer(long delay, int keyIndex, PointerTracker tracker) { + cancelLongPressTimers(); + sendMessageDelayed( + obtainMessage(MSG_LONGPRESS_SHIFT_KEY, keyIndex, 0, tracker), delay); + } + + public void cancelLongPressTimers() { + removeMessages(MSG_LONGPRESS_KEY); + removeMessages(MSG_LONGPRESS_SHIFT_KEY); + } + + public void cancelKeyTimers() { + cancelKeyRepeatTimer(); + cancelLongPressTimers(); + } + + public void cancelAllMessages() { + cancelKeyTimers(); + cancelPopupPreview(); + cancelDismissPreview(); + } + }; + + public KeyboardView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.keyboardViewStyle); + } + + public KeyboardView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView); + LayoutInflater inflate = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + int previewLayout = 0; + int keyTextSize = 0; + + int n = a.getIndexCount(); + + for (int i = 0; i < n; i++) { + int attr = a.getIndex(i); + + switch (attr) { + case R.styleable.KeyboardView_keyBackground: + mKeyBackground = a.getDrawable(attr); + break; + case R.styleable.KeyboardView_keyHysteresisDistance: + mKeyHysteresisDistance = a.getDimensionPixelOffset(attr, 0); + break; + case R.styleable.KeyboardView_verticalCorrection: + mVerticalCorrection = a.getDimensionPixelOffset(attr, 0); + break; + case R.styleable.KeyboardView_keyPreviewLayout: + previewLayout = a.getResourceId(attr, 0); + break; + case R.styleable.KeyboardView_keyPreviewOffset: + mPreviewOffset = a.getDimensionPixelOffset(attr, 0); + break; + case R.styleable.KeyboardView_keyPreviewHeight: + mPreviewHeight = a.getDimensionPixelSize(attr, 80); + break; + case R.styleable.KeyboardView_keyLetterSize: + mKeyLetterSize = a.getDimensionPixelSize(attr, 18); + break; + case R.styleable.KeyboardView_keyTextColor: + mKeyTextColor = a.getColor(attr, 0xFF000000); + break; + case R.styleable.KeyboardView_keyTextColorDisabled: + mKeyTextColorDisabled = a.getColor(attr, 0xFF000000); + break; + case R.styleable.KeyboardView_labelTextSize: + mLabelTextSize = a.getDimensionPixelSize(attr, 14); + break; + case R.styleable.KeyboardView_popupLayout: + mPopupLayout = a.getResourceId(attr, 0); + break; + case R.styleable.KeyboardView_shadowColor: + mShadowColor = a.getColor(attr, 0); + break; + case R.styleable.KeyboardView_shadowRadius: + mShadowRadius = a.getFloat(attr, 0f); + break; + // TODO: Use Theme (android.R.styleable.Theme_backgroundDimAmount) + case R.styleable.KeyboardView_backgroundDimAmount: + mBackgroundDimAmount = a.getFloat(attr, 0.5f); + break; + case R.styleable.KeyboardView_keyLetterStyle: + mKeyLetterStyle = Typeface.defaultFromStyle(a.getInt(attr, Typeface.NORMAL)); + break; + case R.styleable.KeyboardView_colorScheme: + mColorScheme = a.getInt(attr, COLOR_SCHEME_WHITE); + break; + } + } + + final Resources res = getResources(); + + mPreviewPopup = new PopupWindow(context); + if (previewLayout != 0) { + mPreviewText = (TextView) inflate.inflate(previewLayout, null); + mPreviewTextSizeLarge = (int) res.getDimension(R.dimen.key_preview_text_size_large); + mPreviewPopup.setContentView(mPreviewText); + mPreviewPopup.setBackgroundDrawable(null); + } else { + mShowPreview = false; + } + mPreviewPopup.setTouchable(false); + mPreviewPopup.setAnimationStyle(R.style.KeyPreviewAnimation); + mDelayBeforePreview = res.getInteger(R.integer.config_delay_before_preview); + mDelayAfterPreview = res.getInteger(R.integer.config_delay_after_preview); + mKeyLabelHorizontalPadding = (int)res.getDimension( + R.dimen.key_label_horizontal_alignment_padding); + + mMiniKeyboardParent = this; + mMiniKeyboardPopup = new PopupWindow(context); + mMiniKeyboardPopup.setBackgroundDrawable(null); + mMiniKeyboardPopup.setAnimationStyle(R.style.MiniKeyboardAnimation); + + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setTextSize(keyTextSize); + mPaint.setTextAlign(Align.CENTER); + mPaint.setAlpha(255); + + mPadding = new Rect(0, 0, 0, 0); + mKeyBackground.getPadding(mPadding); + + mSwipeThreshold = (int) (500 * res.getDisplayMetrics().density); + // TODO: Refer frameworks/base/core/res/res/values/config.xml + mDisambiguateSwipe = res.getBoolean(R.bool.config_swipeDisambiguation); + mMiniKeyboardSlideAllowance = res.getDimension(R.dimen.mini_keyboard_slide_allowance); + + GestureDetector.SimpleOnGestureListener listener = + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX, + float velocityY) { + final float absX = Math.abs(velocityX); + final float absY = Math.abs(velocityY); + float deltaX = me2.getX() - me1.getX(); + float deltaY = me2.getY() - me1.getY(); + int travelX = getWidth() / 2; // Half the keyboard width + int travelY = getHeight() / 2; // Half the keyboard height + mSwipeTracker.computeCurrentVelocity(1000); + final float endingVelocityX = mSwipeTracker.getXVelocity(); + final float endingVelocityY = mSwipeTracker.getYVelocity(); + if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) { + if (mDisambiguateSwipe && endingVelocityX >= velocityX / 4) { + swipeRight(); + return true; + } + } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) { + if (mDisambiguateSwipe && endingVelocityX <= velocityX / 4) { + swipeLeft(); + return true; + } + } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) { + if (mDisambiguateSwipe && endingVelocityY <= velocityY / 4) { + swipeUp(); + return true; + } + } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) { + if (mDisambiguateSwipe && endingVelocityY >= velocityY / 4) { + swipeDown(); + return true; + } + } + return false; + } + }; + + final boolean ignoreMultitouch = true; + mGestureDetector = new GestureDetector(getContext(), listener, null, ignoreMultitouch); + mGestureDetector.setIsLongpressEnabled(false); + + mHasDistinctMultitouch = context.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); + mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval); + } + + public void setOnKeyboardActionListener(KeyboardActionListener listener) { + mKeyboardActionListener = listener; + for (PointerTracker tracker : mPointerTrackers) { + tracker.setOnKeyboardActionListener(listener); + } + } + + /** + * Returns the {@link KeyboardActionListener} object. + * @return the listener attached to this keyboard + */ + protected KeyboardActionListener getOnKeyboardActionListener() { + return mKeyboardActionListener; + } + + /** + * Attaches a keyboard to this view. The keyboard can be switched at any time and the + * view will re-layout itself to accommodate the keyboard. + * @see Keyboard + * @see #getKeyboard() + * @param keyboard the keyboard to display in this view + */ + public void setKeyboard(Keyboard keyboard) { + if (mKeyboard != null) { + dismissKeyPreview(); + } + // Remove any pending messages, except dismissing preview + mHandler.cancelKeyTimers(); + mHandler.cancelPopupPreview(); + mKeyboard = keyboard; + LatinImeLogger.onSetKeyboard(keyboard); + mKeys = mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), + -getPaddingTop() + mVerticalCorrection); + for (PointerTracker tracker : mPointerTrackers) { + tracker.setKeyboard(keyboard, mKeys, mKeyHysteresisDistance); + } + requestLayout(); + // Hint to reallocate the buffer if the size changed + mKeyboardChanged = true; + invalidateAllKeys(); + computeProximityThreshold(keyboard, mKeys); + mMiniKeyboardCache.clear(); + } + + /** + * Returns the current keyboard being displayed by this view. + * @return the currently attached keyboard + * @see #setKeyboard(Keyboard) + */ + public Keyboard getKeyboard() { + return mKeyboard; + } + + /** + * Return whether the device has distinct multi-touch panel. + * @return true if the device has distinct multi-touch panel. + */ + @Override + public boolean hasDistinctMultitouch() { + return mHasDistinctMultitouch; + } + + /** + * Enables or disables the key feedback popup. This is a popup that shows a magnified + * version of the depressed key. By default the preview is enabled. + * @param previewEnabled whether or not to enable the key feedback popup + * @see #isPreviewEnabled() + */ + public void setPreviewEnabled(boolean previewEnabled) { + mShowPreview = previewEnabled; + } + + /** + * Returns the enabled state of the key feedback popup. + * @return whether or not the key feedback popup is enabled + * @see #setPreviewEnabled(boolean) + */ + public boolean isPreviewEnabled() { + return mShowPreview; + } + + public int getColorScheme() { + return mColorScheme; + } + + public void setPopupParent(View v) { + mMiniKeyboardParent = v; + } + + public void setPopupOffset(int x, int y) { + mPopupPreviewOffsetX = x; + mPopupPreviewOffsetY = y; + mPreviewPopup.dismiss(); + } + + /** + * When enabled, calls to {@link KeyboardActionListener#onKey} will include key + * codes for adjacent keys. When disabled, only the primary key code will be + * reported. + * @param enabled whether or not the proximity correction is enabled + */ + public void setProximityCorrectionEnabled(boolean enabled) { + mKeyDetector.setProximityCorrectionEnabled(enabled); + } + + /** + * Returns true if proximity correction is enabled. + */ + public boolean isProximityCorrectionEnabled() { + return mKeyDetector.isProximityCorrectionEnabled(); + } + + protected CharSequence adjustCase(CharSequence label) { + if (mKeyboard.isShiftedOrShiftLocked() && label != null && label.length() < 3 + && Character.isLowerCase(label.charAt(0))) { + label = label.toString().toUpperCase(); + } + return label; + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Round up a little + if (mKeyboard == null) { + setMeasuredDimension( + getPaddingLeft() + getPaddingRight(), getPaddingTop() + getPaddingBottom()); + } else { + int width = mKeyboard.getMinWidth() + getPaddingLeft() + getPaddingRight(); + if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) { + width = MeasureSpec.getSize(widthMeasureSpec); + } + setMeasuredDimension( + width, mKeyboard.getHeight() + getPaddingTop() + getPaddingBottom()); + } + } + + /** + * Compute the most common key width and use it as proximity key detection threshold. + * @param keyboard + * @param keys + */ + private void computeProximityThreshold(Keyboard keyboard, Key[] keys) { + if (keyboard == null || keys == null || keys.length == 0) return; + final HashMap histogram = new HashMap(); + int maxCount = 0; + int mostCommonWidth = 0; + for (Key key : keys) { + final Integer width = key.width + key.gap; + Integer count = histogram.get(width); + if (count == null) + count = 0; + histogram.put(width, ++count); + if (count > maxCount) { + maxCount = count; + mostCommonWidth = width; + } + } + mKeyDetector.setProximityThreshold(mostCommonWidth); + } + + @Override + public void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + // Release the buffer, if any and it will be reallocated on the next draw + mBuffer = null; + } + + @Override + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mDrawPending || mBuffer == null || mKeyboardChanged) { + onBufferDraw(); + } + canvas.drawBitmap(mBuffer, 0, 0, null); + } + + @SuppressWarnings("unused") + private void onBufferDraw() { + if (mBuffer == null || mKeyboardChanged) { + if (mBuffer == null || mKeyboardChanged && + (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) { + // Make sure our bitmap is at least 1x1 + final int width = Math.max(1, getWidth()); + final int height = Math.max(1, getHeight()); + mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + mCanvas = new Canvas(mBuffer); + } + invalidateAllKeys(); + mKeyboardChanged = false; + } + final Canvas canvas = mCanvas; + canvas.clipRect(mDirtyRect, Op.REPLACE); + + if (mKeyboard == null) return; + + final Paint paint = mPaint; + final Drawable keyBackground = mKeyBackground; + final Rect clipRegion = mClipRegion; + final Rect padding = mPadding; + final int kbdPaddingLeft = getPaddingLeft(); + final int kbdPaddingTop = getPaddingTop(); + final Key[] keys = mKeys; + final Key invalidKey = mInvalidatedKey; + final boolean isManualTemporaryUpperCase = (mKeyboard instanceof LatinKeyboard + && ((LatinKeyboard)mKeyboard).isManualTemporaryUpperCase()); + + boolean drawSingleKey = false; + if (invalidKey != null && canvas.getClipBounds(clipRegion)) { + // TODO we should use Rect.inset and Rect.contains here. + // Is clipRegion completely contained within the invalidated key? + if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left && + invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top && + invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right && + invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) { + drawSingleKey = true; + } + } + canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR); + final int keyCount = keys.length; + for (int i = 0; i < keyCount; i++) { + final Key key = keys[i]; + if (drawSingleKey && invalidKey != key) { + continue; + } + int[] drawableState = key.getCurrentDrawableState(); + keyBackground.setState(drawableState); + + // Switch the character to uppercase if shift is pressed + String label = key.label == null? null : adjustCase(key.label).toString(); + + final Rect bounds = keyBackground.getBounds(); + if (key.width != bounds.right || key.height != bounds.bottom) { + keyBackground.setBounds(0, 0, key.width, key.height); + } + canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop); + keyBackground.draw(canvas); + + final int rowHeight = padding.top + key.height; + // Draw key label + if (label != null) { + // For characters, use large font. For labels like "Done", use small font. + final int labelSize = getLabelSizeAndSetPaint(label, key, paint); + final int labelCharHeight = getLabelCharHeight(labelSize, paint); + + // Vertical label text alignment. + final float baseline; + if ((key.labelOption & KEY_LABEL_OPTION_ALIGN_BOTTOM) != 0) { + baseline = key.height - + + labelCharHeight * KEY_LABEL_VERTICAL_PADDING_FACTOR; + if (DEBUG_SHOW_ALIGN) + drawHorizontalLine(canvas, (int)baseline, key.width, 0xc0008000, + new Paint()); + } else { // Align center + final float centerY = (key.height + padding.top - padding.bottom) / 2; + baseline = centerY + + labelCharHeight * KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR_CENTER; + } + // Horizontal label text alignment + final int positionX; + if ((key.labelOption & KEY_LABEL_OPTION_ALIGN_LEFT) != 0) { + positionX = mKeyLabelHorizontalPadding + padding.left; + paint.setTextAlign(Align.LEFT); + if (DEBUG_SHOW_ALIGN) + drawVerticalLine(canvas, positionX, rowHeight, 0xc0800080, new Paint()); + } else if ((key.labelOption & KEY_LABEL_OPTION_ALIGN_RIGHT) != 0) { + positionX = key.width - mKeyLabelHorizontalPadding - padding.right; + paint.setTextAlign(Align.RIGHT); + if (DEBUG_SHOW_ALIGN) + drawVerticalLine(canvas, positionX, rowHeight, 0xc0808000, new Paint()); + } else { + positionX = (key.width + padding.left - padding.right) / 2; + paint.setTextAlign(Align.CENTER); + if (DEBUG_SHOW_ALIGN && label.length() > 1) + drawVerticalLine(canvas, positionX, rowHeight, 0xc0008080, new Paint()); + } + if (key.manualTemporaryUpperCaseHintIcon != null && isManualTemporaryUpperCase) { + paint.setColor(mKeyTextColorDisabled); + } else { + paint.setColor(mKeyTextColor); + } + // Set a drop shadow for the text + paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor); + canvas.drawText(label, positionX, baseline, paint); + // Turn off drop shadow + paint.setShadowLayer(0, 0, 0, 0); + } + // Draw key icon + if (key.label == null && key.icon != null) { + final int drawableWidth = key.icon.getIntrinsicWidth(); + final int drawableHeight = key.icon.getIntrinsicHeight(); + final int drawableX; + final int drawableY = ( + key.height + padding.top - padding.bottom - drawableHeight) / 2; + if ((key.labelOption & KEY_LABEL_OPTION_ALIGN_LEFT) != 0) { + drawableX = padding.left + mKeyLabelHorizontalPadding; + if (DEBUG_SHOW_ALIGN) + drawVerticalLine(canvas, drawableX, rowHeight, 0xc0800080, new Paint()); + } else if ((key.labelOption & KEY_LABEL_OPTION_ALIGN_RIGHT) != 0) { + drawableX = key.width - padding.right - mKeyLabelHorizontalPadding + - drawableWidth; + if (DEBUG_SHOW_ALIGN) + drawVerticalLine(canvas, drawableX + drawableWidth, rowHeight, + 0xc0808000, new Paint()); + } else { // Align center + drawableX = (key.width + padding.left - padding.right - drawableWidth) / 2; + if (DEBUG_SHOW_ALIGN) + drawVerticalLine(canvas, drawableX + drawableWidth / 2, rowHeight, + 0xc0008080, new Paint()); + } + drawIcon(canvas, key.icon, drawableX, drawableY, drawableWidth, drawableHeight); + if (DEBUG_SHOW_ALIGN) + drawRectangle(canvas, drawableX, drawableY, drawableWidth, drawableHeight, + 0x80c00000, new Paint()); + } + if (key.hintIcon != null) { + final int drawableWidth = key.width; + final int drawableHeight = key.height; + final int drawableX = 0; + final int drawableY = HINT_ICON_VERTICAL_ADJUSTMENT_PIXEL; + Drawable icon = (isManualTemporaryUpperCase + && key.manualTemporaryUpperCaseHintIcon != null) + ? key.manualTemporaryUpperCaseHintIcon : key.hintIcon; + drawIcon(canvas, icon, drawableX, drawableY, drawableWidth, drawableHeight); + if (DEBUG_SHOW_ALIGN) + drawRectangle(canvas, drawableX, drawableY, drawableWidth, drawableHeight, + 0x80c0c000, new Paint()); + } + canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop); + } + + if (DEBUG_KEYBOARD_GRID) { + Paint p = new Paint(); + p.setStyle(Paint.Style.STROKE); + p.setStrokeWidth(1.0f); + p.setColor(0x800000c0); + int cw = (mKeyboard.getMinWidth() + mKeyboard.GRID_WIDTH - 1) / mKeyboard.GRID_WIDTH; + int ch = (mKeyboard.getHeight() + mKeyboard.GRID_HEIGHT - 1) / mKeyboard.GRID_HEIGHT; + for (int i = 0; i <= mKeyboard.GRID_WIDTH; i++) + canvas.drawLine(i * cw, 0, i * cw, ch * mKeyboard.GRID_HEIGHT, p); + for (int i = 0; i <= mKeyboard.GRID_HEIGHT; i++) + canvas.drawLine(0, i * ch, cw * mKeyboard.GRID_WIDTH, i * ch, p); + } + + mInvalidatedKey = null; + // Overlay a dark rectangle to dim the keyboard + if (mMiniKeyboard != null) { + paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24); + canvas.drawRect(0, 0, getWidth(), getHeight(), paint); + } + + if (DEBUG) { + if (mShowTouchPoints) { + for (PointerTracker tracker : mPointerTrackers) { + int startX = tracker.getStartX(); + int startY = tracker.getStartY(); + int lastX = tracker.getLastX(); + int lastY = tracker.getLastY(); + paint.setAlpha(128); + paint.setColor(0xFFFF0000); + canvas.drawCircle(startX, startY, 3, paint); + canvas.drawLine(startX, startY, lastX, lastY, paint); + paint.setColor(0xFF0000FF); + canvas.drawCircle(lastX, lastY, 3, paint); + paint.setColor(0xFF00FF00); + canvas.drawCircle((startX + lastX) / 2, (startY + lastY) / 2, 2, paint); + } + } + } + + mDrawPending = false; + mDirtyRect.setEmpty(); + } + + private int getLabelSizeAndSetPaint(CharSequence label, Key key, Paint paint) { + // For characters, use large font. For labels like "Done", use small font. + final int labelSize; + final Typeface labelStyle; + if (label.length() > 1 && key.codes.length < 2) { + labelSize = mLabelTextSize; + if ((key.labelOption & KEY_LABEL_OPTION_FONT_NORMAL) != 0) { + labelStyle = Typeface.DEFAULT; + } else { + labelStyle = Typeface.DEFAULT_BOLD; + } + } else { + labelSize = mKeyLetterSize; + labelStyle = mKeyLetterStyle; + } + paint.setTextSize(labelSize); + paint.setTypeface(labelStyle); + return labelSize; + } + + private int getLabelCharHeight(int labelSize, Paint paint) { + Integer labelHeightValue = mTextHeightCache.get(labelSize); + final int labelCharHeight; + if (labelHeightValue != null) { + labelCharHeight = labelHeightValue; + } else { + Rect textBounds = new Rect(); + paint.getTextBounds(KEY_LABEL_REFERENCE_CHAR, 0, 1, textBounds); + labelCharHeight = textBounds.height(); + mTextHeightCache.put(labelSize, labelCharHeight); + } + return labelCharHeight; + } + + private static void drawIcon(Canvas canvas, Drawable icon, int x, int y, int width, + int height) { + canvas.translate(x, y); + icon.setBounds(0, 0, width, height); + icon.draw(canvas); + canvas.translate(-x, -y); + } + + private static void drawHorizontalLine(Canvas canvas, int y, int w, int color, Paint paint) { + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(1.0f); + paint.setColor(color); + canvas.drawLine(0, y, w, y, paint); + } + + private static void drawVerticalLine(Canvas canvas, int x, int h, int color, Paint paint) { + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(1.0f); + paint.setColor(color); + canvas.drawLine(x, 0, x, h, paint); + } + + private static void drawRectangle(Canvas canvas, int x, int y, int w, int h, int color, + Paint paint) { + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(1.0f); + paint.setColor(color); + canvas.translate(x, y); + canvas.drawRect(0, 0, w, h, paint); + canvas.translate(-x, -y); + } + + public void setForeground(boolean foreground) { + mInForeground = foreground; + } + + // TODO: clean up this method. + private void dismissKeyPreview() { + for (PointerTracker tracker : mPointerTrackers) + tracker.releaseKey(); + showPreview(KeyDetector.NOT_A_KEY, null); + } + + @Override + public void showPreview(int keyIndex, PointerTracker tracker) { + int oldKeyIndex = mOldPreviewKeyIndex; + mOldPreviewKeyIndex = keyIndex; + // We should re-draw popup preview when 1) we need to hide the preview, 2) we will show + // the space key preview and 3) pointer moves off the space key to other letter key, we + // should hide the preview of the previous key. + @SuppressWarnings("unused") + final boolean hidePreviewOrShowSpaceKeyPreview = (tracker == null) + || (SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCHER + && SubtypeSwitcher.getInstance().needsToDisplayLanguage() + && (tracker.isSpaceKey(keyIndex) || tracker.isSpaceKey(oldKeyIndex))); + // If key changed and preview is on or the key is space (language switch is enabled) + if (oldKeyIndex != keyIndex && (mShowPreview || (hidePreviewOrShowSpaceKeyPreview))) { + if (keyIndex == KeyDetector.NOT_A_KEY) { + mHandler.cancelPopupPreview(); + mHandler.dismissPreview(mDelayAfterPreview); + } else if (tracker != null) { + mHandler.popupPreview(mDelayBeforePreview, keyIndex, tracker); + } + } + } + + // TODO Must fix popup preview on xlarge layout + private void showKey(final int keyIndex, PointerTracker tracker) { + Key key = tracker.getKey(keyIndex); + // If keyIndex is invalid or IME is already closed, we must not show key preview. + // Trying to show preview PopupWindow while root window is closed causes + // WindowManager.BadTokenException. + if (key == null || !mInForeground) + return; + // What we show as preview should match what we show on key top in onBufferDraw(). + if (key.label != null) { + // TODO Should take care of temporaryShiftLabel here. + mPreviewText.setCompoundDrawables(null, null, null, null); + mPreviewText.setText(adjustCase(tracker.getPreviewText(key))); + if (key.label.length() > 1 && key.codes.length < 2) { + mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyLetterSize); + mPreviewText.setTypeface(Typeface.DEFAULT_BOLD); + } else { + mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge); + mPreviewText.setTypeface(mKeyLetterStyle); + } + } else { + mPreviewText.setCompoundDrawables(null, null, null, + key.iconPreview != null ? key.iconPreview : key.icon); + mPreviewText.setText(null); + } + mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width + + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight()); + final int popupHeight = mPreviewHeight; + LayoutParams lp = mPreviewText.getLayoutParams(); + if (lp != null) { + lp.width = popupWidth; + lp.height = popupHeight; + } + + int popupPreviewX = key.x - (popupWidth - key.width) / 2; + int popupPreviewY = key.y - popupHeight + mPreviewOffset; + + mHandler.cancelDismissPreview(); + if (mOffsetInWindow == null) { + mOffsetInWindow = new int[2]; + getLocationInWindow(mOffsetInWindow); + mOffsetInWindow[0] += mPopupPreviewOffsetX; // Offset may be zero + mOffsetInWindow[1] += mPopupPreviewOffsetY; // Offset may be zero + int[] windowLocation = new int[2]; + getLocationOnScreen(windowLocation); + mWindowY = windowLocation[1]; + } + // Set the preview background state + mPreviewText.getBackground().setState( + key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET); + popupPreviewX += mOffsetInWindow[0]; + popupPreviewY += mOffsetInWindow[1]; + + // If the popup cannot be shown above the key, put it on the side + if (popupPreviewY + mWindowY < 0) { + // If the key you're pressing is on the left side of the keyboard, show the popup on + // the right, offset by enough to see at least one key to the left/right. + if (key.x + key.width <= getWidth() / 2) { + popupPreviewX += (int) (key.width * 2.5); + } else { + popupPreviewX -= (int) (key.width * 2.5); + } + popupPreviewY += popupHeight; + } + + try { + if (mPreviewPopup.isShowing()) { + mPreviewPopup.update(popupPreviewX, popupPreviewY, popupWidth, popupHeight); + } else { + mPreviewPopup.setWidth(popupWidth); + mPreviewPopup.setHeight(popupHeight); + mPreviewPopup.showAtLocation(mMiniKeyboardParent, Gravity.NO_GRAVITY, + popupPreviewX, popupPreviewY); + } + } catch (WindowManager.BadTokenException e) { + // Swallow the exception which will be happened when IME is already closed. + Log.w(TAG, "LatinIME is already closed when tried showing key preview."); + } + // Record popup preview position to display mini-keyboard later at the same positon + mPopupPreviewDisplayedY = popupPreviewY; + mPreviewText.setVisibility(VISIBLE); + } + + /** + * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient + * because the keyboard renders the keys to an off-screen buffer and an invalidate() only + * draws the cached buffer. + * @see #invalidateKey(Key) + */ + public void invalidateAllKeys() { + mDirtyRect.union(0, 0, getWidth(), getHeight()); + mDrawPending = true; + invalidate(); + } + + /** + * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only + * one key is changing it's content. Any changes that affect the position or size of the key + * may not be honored. + * @param key key in the attached {@link Keyboard}. + * @see #invalidateAllKeys + */ + @Override + public void invalidateKey(Key key) { + if (key == null) + return; + mInvalidatedKey = key; + // TODO we should clean up this and record key's region to use in onBufferDraw. + mDirtyRect.union(key.x + getPaddingLeft(), key.y + getPaddingTop(), + key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop()); + onBufferDraw(); + invalidate(key.x + getPaddingLeft(), key.y + getPaddingTop(), + key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop()); + } + + private boolean openPopupIfRequired(int keyIndex, PointerTracker tracker) { + // Check if we have a popup layout specified first. + if (mPopupLayout == 0) { + return false; + } + + Key popupKey = tracker.getKey(keyIndex); + if (popupKey == null) + return false; + boolean result = onLongPress(popupKey); + if (result) { + dismissKeyPreview(); + mMiniKeyboardTrackerId = tracker.mPointerId; + // Mark this tracker "already processed" and remove it from the pointer queue + tracker.setAlreadyProcessed(); + mPointerQueue.remove(tracker); + } + return result; + } + + private void onLongPressShiftKey(PointerTracker tracker) { + tracker.setAlreadyProcessed(); + mPointerQueue.remove(tracker); + mKeyboardActionListener.onKey(LatinKeyboard.KEYCODE_CAPSLOCK, null, 0, 0); + } + + private View inflateMiniKeyboardContainer(Key popupKey) { + int popupKeyboardId = popupKey.popupResId; + LayoutInflater inflater = (LayoutInflater)getContext().getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + View container = inflater.inflate(mPopupLayout, null); + if (container == null) + throw new NullPointerException(); + + KeyboardView miniKeyboard = + (KeyboardView)container.findViewById(R.id.KeyboardView); + miniKeyboard.setOnKeyboardActionListener(new KeyboardActionListener() { + @Override + public void onKey(int primaryCode, int[] keyCodes, int x, int y) { + mKeyboardActionListener.onKey(primaryCode, keyCodes, x, y); + dismissPopupKeyboard(); + } + + @Override + public void onText(CharSequence text) { + mKeyboardActionListener.onText(text); + dismissPopupKeyboard(); + } + + @Override + public void onCancel() { + dismissPopupKeyboard(); + } + + @Override + public void swipeLeft() { + } + @Override + public void swipeRight() { + } + @Override + public void swipeUp() { + } + @Override + public void swipeDown() { + } + @Override + public void onPress(int primaryCode) { + mKeyboardActionListener.onPress(primaryCode); + } + @Override + public void onRelease(int primaryCode) { + mKeyboardActionListener.onRelease(primaryCode); + } + }); + // Override default ProximityKeyDetector. + miniKeyboard.mKeyDetector = new MiniKeyboardKeyDetector(mMiniKeyboardSlideAllowance); + // Remove gesture detector on mini-keyboard + miniKeyboard.mGestureDetector = null; + + Keyboard keyboard; + if (popupKey.popupCharacters != null) { + keyboard = new Keyboard(getContext(), popupKeyboardId, popupKey.popupCharacters, + -1, getPaddingLeft() + getPaddingRight()); + } else { + keyboard = new Keyboard(getContext(), popupKeyboardId); + } + miniKeyboard.setKeyboard(keyboard); + miniKeyboard.setPopupParent(this); + + container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST)); + + return container; + } + + private static boolean isOneRowKeys(List keys) { + if (keys.size() == 0) return false; + final int edgeFlags = keys.get(0).edgeFlags; + // HACK: The first key of mini keyboard which was inflated from xml and has multiple rows, + // does not have both top and bottom edge flags on at the same time. On the other hand, + // the first key of mini keyboard that was created with popupCharacters must have both top + // and bottom edge flags on. + // When you want to use one row mini-keyboard from xml file, make sure that the row has + // both top and bottom edge flags set. + return (edgeFlags & Keyboard.EDGE_TOP) != 0 + && (edgeFlags & Keyboard.EDGE_BOTTOM) != 0; + } + + /** + * Called when a key is long pressed. By default this will open any popup keyboard associated + * with this key through the attributes popupLayout and popupCharacters. + * @param popupKey the key that was long pressed + * @return true if the long press is handled, false otherwise. Subclasses should call the + * method on the base class if the subclass doesn't wish to handle the call. + */ + protected boolean onLongPress(Key popupKey) { + // TODO if popupKey.popupCharacters has only one letter, send it as key without opening + // mini keyboard. + + if (popupKey.popupResId == 0) + return false; + + View container = mMiniKeyboardCache.get(popupKey); + if (container == null) { + container = inflateMiniKeyboardContainer(popupKey); + mMiniKeyboardCache.put(popupKey, container); + } + mMiniKeyboard = (KeyboardView)container.findViewById(R.id.KeyboardView); + if (mWindowOffset == null) { + mWindowOffset = new int[2]; + getLocationInWindow(mWindowOffset); + } + + // Get width of a key in the mini popup keyboard = "miniKeyWidth". + // On the other hand, "popupKey.width" is width of the pressed key on the main keyboard. + // We adjust the position of mini popup keyboard with the edge key in it: + // a) When we have the leftmost key in popup keyboard directly above the pressed key + // Right edges of both keys should be aligned for consistent default selection + // b) When we have the rightmost key in popup keyboard directly above the pressed key + // Left edges of both keys should be aligned for consistent default selection + final List miniKeys = mMiniKeyboard.getKeyboard().getKeys(); + final int miniKeyWidth = miniKeys.size() > 0 ? miniKeys.get(0).width : 0; + + // HACK: Have the leftmost number in the popup characters right above the key + boolean isNumberAtLeftmost = + hasMultiplePopupChars(popupKey) && isNumberAtLeftmostPopupChar(popupKey); + int popupX = popupKey.x + mWindowOffset[0]; + popupX += getPaddingLeft(); + if (isNumberAtLeftmost) { + popupX += popupKey.width - miniKeyWidth; // adjustment for a) described above + popupX -= container.getPaddingLeft(); + } else { + popupX += miniKeyWidth; // adjustment for b) described above + popupX -= container.getMeasuredWidth(); + popupX += container.getPaddingRight(); + } + int popupY = popupKey.y + mWindowOffset[1]; + popupY += getPaddingTop(); + popupY -= container.getMeasuredHeight(); + popupY += container.getPaddingBottom(); + final int x = popupX; + final int y = mShowPreview && isOneRowKeys(miniKeys) ? mPopupPreviewDisplayedY : popupY; + + int adjustedX = x; + if (x < 0) { + adjustedX = 0; + } else if (x > (getMeasuredWidth() - container.getMeasuredWidth())) { + adjustedX = getMeasuredWidth() - container.getMeasuredWidth(); + } + mMiniKeyboardOriginX = adjustedX + container.getPaddingLeft() - mWindowOffset[0]; + mMiniKeyboardOriginY = y + container.getPaddingTop() - mWindowOffset[1]; + mMiniKeyboard.setPopupOffset(adjustedX, y); + // TODO: change the below line to use getLatinKeyboard() instead of getKeyboard() + Keyboard baseMiniKeyboard = mMiniKeyboard.getKeyboard(); + if (baseMiniKeyboard != null && baseMiniKeyboard.setShifted(mKeyboard == null + ? false : mKeyboard.isShiftedOrShiftLocked())) { + mMiniKeyboard.invalidateAllKeys(); + } + // Mini keyboard needs no pop-up key preview displayed. + mMiniKeyboard.setPreviewEnabled(false); + mMiniKeyboardPopup.setContentView(container); + mMiniKeyboardPopup.setWidth(container.getMeasuredWidth()); + mMiniKeyboardPopup.setHeight(container.getMeasuredHeight()); + mMiniKeyboardPopup.showAtLocation(this, Gravity.NO_GRAVITY, x, y); + + // Inject down event on the key to mini keyboard. + long eventTime = SystemClock.uptimeMillis(); + mMiniKeyboardPopupTime = eventTime; + MotionEvent downEvent = generateMiniKeyboardMotionEvent(MotionEvent.ACTION_DOWN, popupKey.x + + popupKey.width / 2, popupKey.y + popupKey.height / 2, eventTime); + mMiniKeyboard.onTouchEvent(downEvent); + downEvent.recycle(); + + invalidateAllKeys(); + return true; + } + + private static boolean hasMultiplePopupChars(Key key) { + if (key.popupCharacters != null && key.popupCharacters.length() > 1) { + return true; + } + return false; + } + + private static boolean isNumberAtLeftmostPopupChar(Key key) { + if (key.popupCharacters != null && key.popupCharacters.length() > 0 + && isAsciiDigit(key.popupCharacters.charAt(0))) { + return true; + } + return false; + } + + private static boolean isAsciiDigit(char c) { + return (c < 0x80) && Character.isDigit(c); + } + + private MotionEvent generateMiniKeyboardMotionEvent(int action, int x, int y, long eventTime) { + return MotionEvent.obtain(mMiniKeyboardPopupTime, eventTime, action, + x - mMiniKeyboardOriginX, y - mMiniKeyboardOriginY, 0); + } + + private PointerTracker getPointerTracker(final int id) { + final ArrayList pointers = mPointerTrackers; + final Key[] keys = mKeys; + final KeyboardActionListener listener = mKeyboardActionListener; + + // Create pointer trackers until we can get 'id+1'-th tracker, if needed. + for (int i = pointers.size(); i <= id; i++) { + final PointerTracker tracker = + new PointerTracker(i, mHandler, mKeyDetector, this, getResources()); + if (keys != null) + tracker.setKeyboard(mKeyboard, keys, mKeyHysteresisDistance); + if (listener != null) + tracker.setOnKeyboardActionListener(listener); + pointers.add(tracker); + } + + return pointers.get(id); + } + + @Override + public boolean onTouchEvent(MotionEvent me) { + final int pointerCount = me.getPointerCount(); + final int action = me.getActionMasked(); + + // TODO: cleanup this code into a multi-touch to single-touch event converter class? + // If the device does not have distinct multi-touch support panel, ignore all multi-touch + // events except a transition from/to single-touch. + if (!mHasDistinctMultitouch && pointerCount > 1 && mOldPointerCount > 1) { + return true; + } + + // Track the last few movements to look for spurious swipes. + mSwipeTracker.addMovement(me); + + // Gesture detector must be enabled only when mini-keyboard is not on the screen. + if (mMiniKeyboard == null + && mGestureDetector != null && mGestureDetector.onTouchEvent(me)) { + dismissKeyPreview(); + mHandler.cancelKeyTimers(); + return true; + } + + final long eventTime = me.getEventTime(); + final int index = me.getActionIndex(); + final int id = me.getPointerId(index); + final int x = (int)me.getX(index); + final int y = (int)me.getY(index); + + // Needs to be called after the gesture detector gets a turn, as it may have + // displayed the mini keyboard + if (mMiniKeyboard != null) { + final int miniKeyboardPointerIndex = me.findPointerIndex(mMiniKeyboardTrackerId); + if (miniKeyboardPointerIndex >= 0 && miniKeyboardPointerIndex < pointerCount) { + final int miniKeyboardX = (int)me.getX(miniKeyboardPointerIndex); + final int miniKeyboardY = (int)me.getY(miniKeyboardPointerIndex); + MotionEvent translated = generateMiniKeyboardMotionEvent(action, + miniKeyboardX, miniKeyboardY, eventTime); + mMiniKeyboard.onTouchEvent(translated); + translated.recycle(); + } + return true; + } + + if (mHandler.isInKeyRepeat()) { + // It will keep being in the key repeating mode while the key is being pressed. + if (action == MotionEvent.ACTION_MOVE) { + return true; + } + final PointerTracker tracker = getPointerTracker(id); + // Key repeating timer will be canceled if 2 or more keys are in action, and current + // event (UP or DOWN) is non-modifier key. + if (pointerCount > 1 && !tracker.isModifier()) { + mHandler.cancelKeyRepeatTimer(); + } + // Up event will pass through. + } + + // TODO: cleanup this code into a multi-touch to single-touch event converter class? + // Translate mutli-touch event to single-touch events on the device that has no distinct + // multi-touch panel. + if (!mHasDistinctMultitouch) { + // Use only main (id=0) pointer tracker. + PointerTracker tracker = getPointerTracker(0); + int oldPointerCount = mOldPointerCount; + if (pointerCount == 1 && oldPointerCount == 2) { + // Multi-touch to single touch transition. + // Send a down event for the latest pointer. + tracker.onDownEvent(x, y, eventTime); + } else if (pointerCount == 2 && oldPointerCount == 1) { + // Single-touch to multi-touch transition. + // Send an up event for the last pointer. + tracker.onUpEvent(tracker.getLastX(), tracker.getLastY(), eventTime); + } else if (pointerCount == 1 && oldPointerCount == 1) { + tracker.onTouchEvent(action, x, y, eventTime); + } else { + Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount + + " (old " + oldPointerCount + ")"); + } + mOldPointerCount = pointerCount; + return true; + } + + if (action == MotionEvent.ACTION_MOVE) { + for (int i = 0; i < pointerCount; i++) { + PointerTracker tracker = getPointerTracker(me.getPointerId(i)); + tracker.onMoveEvent((int)me.getX(i), (int)me.getY(i), eventTime); + } + } else { + PointerTracker tracker = getPointerTracker(id); + switch (action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + onDownEvent(tracker, x, y, eventTime); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + onUpEvent(tracker, x, y, eventTime); + break; + case MotionEvent.ACTION_CANCEL: + onCancelEvent(tracker, x, y, eventTime); + break; + } + } + + return true; + } + + private void onDownEvent(PointerTracker tracker, int x, int y, long eventTime) { + if (tracker.isOnModifierKey(x, y)) { + // Before processing a down event of modifier key, all pointers already being tracked + // should be released. + mPointerQueue.releaseAllPointersExcept(null, eventTime); + } + tracker.onDownEvent(x, y, eventTime); + mPointerQueue.add(tracker); + } + + private void onUpEvent(PointerTracker tracker, int x, int y, long eventTime) { + if (tracker.isModifier()) { + // Before processing an up event of modifier key, all pointers already being tracked + // should be released. + mPointerQueue.releaseAllPointersExcept(tracker, eventTime); + } else { + int index = mPointerQueue.lastIndexOf(tracker); + if (index >= 0) { + mPointerQueue.releaseAllPointersOlderThan(tracker, eventTime); + } else { + Log.w(TAG, "onUpEvent: corresponding down event not found for pointer " + + tracker.mPointerId); + } + } + tracker.onUpEvent(x, y, eventTime); + mPointerQueue.remove(tracker); + } + + private void onCancelEvent(PointerTracker tracker, int x, int y, long eventTime) { + tracker.onCancelEvent(x, y, eventTime); + mPointerQueue.remove(tracker); + } + + protected void swipeRight() { + mKeyboardActionListener.swipeRight(); + } + + protected void swipeLeft() { + mKeyboardActionListener.swipeLeft(); + } + + protected void swipeUp() { + mKeyboardActionListener.swipeUp(); + } + + protected void swipeDown() { + mKeyboardActionListener.swipeDown(); + } + + public void closing() { + mPreviewPopup.dismiss(); + mHandler.cancelAllMessages(); + + dismissPopupKeyboard(); + mBuffer = null; + mCanvas = null; + mMiniKeyboardCache.clear(); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + closing(); + } + + private void dismissPopupKeyboard() { + if (mMiniKeyboardPopup.isShowing()) { + mMiniKeyboardPopup.dismiss(); + mMiniKeyboard = null; + mMiniKeyboardOriginX = 0; + mMiniKeyboardOriginY = 0; + invalidateAllKeys(); + } + } + + public boolean handleBack() { + if (mMiniKeyboardPopup.isShowing()) { + dismissPopupKeyboard(); + return true; + } + return false; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/LatinKey.java b/java/src/com/android/inputmethod/keyboard/LatinKey.java new file mode 100644 index 000000000..4eaf4c8f6 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/LatinKey.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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.res.Resources; +import android.content.res.XmlResourceParser; + +public class LatinKey extends Key { + + // functional normal state (with properties) + private final int[] KEY_STATE_FUNCTIONAL_NORMAL = { + android.R.attr.state_single + }; + + // functional pressed state (with properties) + private final int[] KEY_STATE_FUNCTIONAL_PRESSED = { + android.R.attr.state_single, + android.R.attr.state_pressed + }; + + private boolean mShiftLockEnabled; + + public LatinKey(Resources res, Row parent, int x, int y, + XmlResourceParser parser, KeyStyles keyStyles) { + super(res, parent, x, y, parser, keyStyles); + if (popupCharacters != null && popupCharacters.length() == 0) { + // If there is a keyboard with no keys specified in popupCharacters + popupResId = 0; + } + } + + void enableShiftLock() { + mShiftLockEnabled = true; + } + + // sticky is used for shift key. If a key is not sticky and is modifier, + // the key will be treated as functional. + private boolean isFunctionalKey() { + return !sticky && modifier; + } + + @Override + public void onReleased(boolean inside) { + if (!mShiftLockEnabled) { + super.onReleased(inside); + } else { + pressed = !pressed; + } + } + + /** + * Overriding this method so that we can reduce the target area for certain keys. + */ + @Override + public boolean isInside(int x, int y) { + boolean result = (keyboard instanceof LatinKeyboard) + && ((LatinKeyboard)keyboard).isInside(this, x, y); + return result; + } + + boolean isInsideSuper(int x, int y) { + return super.isInside(x, y); + } + + @Override + public int[] getCurrentDrawableState() { + if (isFunctionalKey()) { + if (pressed) { + return KEY_STATE_FUNCTIONAL_PRESSED; + } else { + return KEY_STATE_FUNCTIONAL_NORMAL; + } + } + return super.getCurrentDrawableState(); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java new file mode 100644 index 000000000..29f749a46 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java @@ -0,0 +1,543 @@ +/* + * Copyright (C) 2008 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 com.android.inputmethod.latin.LatinIME; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.SubtypeSwitcher; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.Log; + +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class LatinKeyboard extends Keyboard { + + private static final boolean DEBUG_PREFERRED_LETTER = false; + private static final String TAG = "LatinKeyboard"; + + public static final int KEYCODE_OPTIONS = -100; + public static final int KEYCODE_OPTIONS_LONGPRESS = -101; + // TODO: remove this once LatinIME stops referring to this. + public static final int KEYCODE_VOICE = -102; + public static final int KEYCODE_NEXT_LANGUAGE = -104; + public static final int KEYCODE_PREV_LANGUAGE = -105; + public static final int KEYCODE_CAPSLOCK = -106; + + static final int OPACITY_FULLY_OPAQUE = 255; + private static final int SPACE_LED_LENGTH_PERCENT = 80; + + private Drawable mShiftLockPreviewIcon; + private final HashMap mNormalShiftIcons = new HashMap(); + private Drawable mSpaceIcon; + private Drawable mSpaceAutoCompletionIndicator; + private Drawable mSpacePreviewIcon; + private final Drawable mButtonArrowLeftIcon; + private final Drawable mButtonArrowRightIcon; + private final int mSpaceBarTextShadowColor; + private Key mSpaceKey; + private int mSpaceKeyIndex = -1; + private int mSpaceDragStartX; + private int mSpaceDragLastDiff; + private final Resources mRes; + private final Context mContext; + private boolean mCurrentlyInSpace; + private SlidingLocaleDrawable mSlidingLocaleIcon; + private int[] mPrefLetterFrequencies; + private int mPrefLetter; + private int mPrefLetterX; + private int mPrefLetterY; + private int mPrefDistance; + + private LatinKeyboardShiftState mShiftState = new LatinKeyboardShiftState(); + + private static final float SPACEBAR_DRAG_THRESHOLD = 0.8f; + private static final float OVERLAP_PERCENTAGE_LOW_PROB = 0.70f; + private static final float OVERLAP_PERCENTAGE_HIGH_PROB = 0.85f; + // Minimum width of space key preview (proportional to keyboard width) + private static final float SPACEBAR_POPUP_MIN_RATIO = 0.4f; + // Height in space key the language name will be drawn. (proportional to space key height) + public static final float SPACEBAR_LANGUAGE_BASELINE = 0.6f; + // If the full language name needs to be smaller than this value to be drawn on space key, + // its short language name will be used instead. + private static final float MINIMUM_SCALE_OF_LANGUAGE_NAME = 0.8f; + + private static int sSpacebarVerticalCorrection; + + public LatinKeyboard(Context context, KeyboardId id) { + super(context, id); + final Resources res = context.getResources(); + mContext = context; + mRes = res; + if (id.mColorScheme == KeyboardView.COLOR_SCHEME_BLACK) { + mSpaceBarTextShadowColor = res.getColor( + R.color.latinkeyboard_bar_language_shadow_black); + } else { // default color scheme is KeyboardView.COLOR_SCHEME_WHITE + mSpaceBarTextShadowColor = res.getColor( + R.color.latinkeyboard_bar_language_shadow_white); + } + mShiftLockPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_shift_locked); + setDefaultBounds(mShiftLockPreviewIcon); + mSpaceAutoCompletionIndicator = res.getDrawable(R.drawable.sym_keyboard_space_led); + mButtonArrowLeftIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_left); + mButtonArrowRightIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_right); + sSpacebarVerticalCorrection = res.getDimensionPixelOffset( + R.dimen.spacebar_vertical_correction); + mSpaceKeyIndex = indexOf(LatinIME.KEYCODE_SPACE); + } + + @Override + protected Key createKeyFromXml(Resources res, Row parent, int x, int y, + XmlResourceParser parser, KeyStyles keyStyles) { + Key key = new LatinKey(res, parent, x, y, parser, keyStyles); + switch (key.codes[0]) { + case LatinIME.KEYCODE_SPACE: + mSpaceKey = key; + mSpaceIcon = key.icon; + mSpacePreviewIcon = key.iconPreview; + break; + } + + return key; + } + + public void enableShiftLock() { + for (final Key key : getShiftKeys()) { + if (key instanceof LatinKey) { + ((LatinKey)key).enableShiftLock(); + } + mNormalShiftIcons.put(key, key.icon); + } + } + + public boolean setShiftLocked(boolean newShiftLockState) { + final Map shiftedIcons = getShiftedIcons(); + for (final Key key : getShiftKeys()) { + key.on = newShiftLockState; + key.icon = newShiftLockState ? shiftedIcons.get(key) : mNormalShiftIcons.get(key); + } + mShiftState.setShiftLocked(newShiftLockState); + return true; + } + + public boolean isShiftLocked() { + return mShiftState.isShiftLocked(); + } + + @Override + public boolean setShifted(boolean newShiftState) { + if (getShiftKeys().size() == 0) + return super.setShifted(newShiftState); + + final Map shiftedIcons = getShiftedIcons(); + for (final Key key : getShiftKeys()) { + if (!newShiftState && !mShiftState.isShiftLocked()) { + key.icon = mNormalShiftIcons.get(key); + } else if (newShiftState && !mShiftState.isShiftedOrShiftLocked()) { + key.icon = shiftedIcons.get(key); + } + } + return mShiftState.setShifted(newShiftState); + } + + @Override + public boolean isShiftedOrShiftLocked() { + if (getShiftKeys().size() > 0) { + return mShiftState.isShiftedOrShiftLocked(); + } else { + return super.isShiftedOrShiftLocked(); + } + } + + public void setAutomaticTemporaryUpperCase() { + setShifted(true); + mShiftState.setAutomaticTemporaryUpperCase(); + } + + public boolean isAutomaticTemporaryUpperCase() { + return isAlphaKeyboard() && mShiftState.isAutomaticTemporaryUpperCase(); + } + + public boolean isManualTemporaryUpperCase() { + return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCase(); + } + + public LatinKeyboardShiftState getKeyboardShiftState() { + return mShiftState; + } + + public boolean isAlphaKeyboard() { + return mId.getXmlId() == R.xml.kbd_qwerty; + } + + public boolean isPhoneKeyboard() { + return mId.mMode == KeyboardId.MODE_PHONE; + } + + public boolean isNumberKeyboard() { + return mId.mMode == KeyboardId.MODE_NUMBER; + } + + /** + * @return a key which should be invalidated. + */ + public Key onAutoCompletionStateChanged(boolean isAutoCompletion) { + updateSpaceBarForLocale(isAutoCompletion); + return mSpaceKey; + } + + private void updateSpaceBarForLocale(boolean isAutoCompletion) { + final Resources res = mRes; + // If application locales are explicitly selected. + if (SubtypeSwitcher.getInstance().needsToDisplayLanguage()) { + mSpaceKey.icon = new BitmapDrawable(res, + drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCompletion)); + } else { + // sym_keyboard_space_led can be shared with Black and White symbol themes. + if (isAutoCompletion) { + mSpaceKey.icon = new BitmapDrawable(res, + drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCompletion)); + } else { + mSpaceKey.icon = mSpaceIcon; + } + } + } + + // Compute width of text with specified text size using paint. + private static int getTextWidth(Paint paint, String text, float textSize, Rect bounds) { + paint.setTextSize(textSize); + paint.getTextBounds(text, 0, text.length(), bounds); + return bounds.width(); + } + + // Layout local language name and left and right arrow on space bar. + private static String layoutSpaceBar(Paint paint, Locale locale, Drawable lArrow, + Drawable rArrow, int width, int height, float origTextSize, + boolean allowVariableTextSize) { + final float arrowWidth = lArrow.getIntrinsicWidth(); + final float arrowHeight = lArrow.getIntrinsicHeight(); + final float maxTextWidth = width - (arrowWidth + arrowWidth); + final Rect bounds = new Rect(); + + // Estimate appropriate language name text size to fit in maxTextWidth. + String language = SubtypeSwitcher.getDisplayLanguage(locale); + int textWidth = getTextWidth(paint, language, origTextSize, bounds); + // Assuming text width and text size are proportional to each other. + float textSize = origTextSize * Math.min(maxTextWidth / textWidth, 1.0f); + + final boolean useShortName; + if (allowVariableTextSize) { + textWidth = getTextWidth(paint, language, textSize, bounds); + // If text size goes too small or text does not fit, use short name + useShortName = textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME + || textWidth > maxTextWidth; + } else { + useShortName = textWidth > maxTextWidth; + textSize = origTextSize; + } + if (useShortName) { + language = SubtypeSwitcher.getShortDisplayLanguage(locale); + textWidth = getTextWidth(paint, language, origTextSize, bounds); + textSize = origTextSize * Math.min(maxTextWidth / textWidth, 1.0f); + } + paint.setTextSize(textSize); + + // Place left and right arrow just before and after language text. + final float baseline = height * SPACEBAR_LANGUAGE_BASELINE; + final int top = (int)(baseline - arrowHeight); + final float remains = (width - textWidth) / 2; + lArrow.setBounds((int)(remains - arrowWidth), top, (int)remains, (int)baseline); + rArrow.setBounds((int)(remains + textWidth), top, (int)(remains + textWidth + arrowWidth), + (int)baseline); + + return language; + } + + @SuppressWarnings("unused") + private Bitmap drawSpaceBar(int opacity, boolean isAutoCompletion) { + final int width = mSpaceKey.width; + final int height = mSpaceIcon.getIntrinsicHeight(); + final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(buffer); + final Resources res = mRes; + canvas.drawColor(res.getColor(R.color.latinkeyboard_transparent), PorterDuff.Mode.CLEAR); + + SubtypeSwitcher subtypeSwitcher = SubtypeSwitcher.getInstance(); + // If application locales are explicitly selected. + if (subtypeSwitcher.needsToDisplayLanguage()) { + final Paint paint = new Paint(); + paint.setAlpha(opacity); + paint.setAntiAlias(true); + paint.setTextAlign(Align.CENTER); + + final boolean allowVariableTextSize = true; + final String language = layoutSpaceBar(paint, subtypeSwitcher.getInputLocale(), + mButtonArrowLeftIcon, mButtonArrowRightIcon, width, height, + getTextSizeFromTheme(android.R.style.TextAppearance_Small, 14), + allowVariableTextSize); + + // Draw language text with shadow + final float baseline = height * SPACEBAR_LANGUAGE_BASELINE; + final float descent = paint.descent(); + paint.setColor(mSpaceBarTextShadowColor); + canvas.drawText(language, width / 2, baseline - descent - 1, paint); + paint.setColor(res.getColor(R.color.latinkeyboard_bar_language_text)); + canvas.drawText(language, width / 2, baseline - descent, paint); + + // Put arrows that are already layed out on either side of the text + if (SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCHER + && subtypeSwitcher.getEnabledKeyboardLocaleCount() > 1) { + mButtonArrowLeftIcon.draw(canvas); + mButtonArrowRightIcon.draw(canvas); + } + } + + // Draw the spacebar icon at the bottom + if (isAutoCompletion) { + final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100; + final int iconHeight = mSpaceAutoCompletionIndicator.getIntrinsicHeight(); + int x = (width - iconWidth) / 2; + int y = height - iconHeight; + mSpaceAutoCompletionIndicator.setBounds(x, y, x + iconWidth, y + iconHeight); + mSpaceAutoCompletionIndicator.draw(canvas); + } else { + final int iconWidth = mSpaceIcon.getIntrinsicWidth(); + final int iconHeight = mSpaceIcon.getIntrinsicHeight(); + int x = (width - iconWidth) / 2; + int y = height - iconHeight; + mSpaceIcon.setBounds(x, y, x + iconWidth, y + iconHeight); + mSpaceIcon.draw(canvas); + } + return buffer; + } + + private void updateLocaleDrag(int diff) { + if (mSlidingLocaleIcon == null) { + final int width = Math.max(mSpaceKey.width, + (int)(getMinWidth() * SPACEBAR_POPUP_MIN_RATIO)); + final int height = mSpacePreviewIcon.getIntrinsicHeight(); + mSlidingLocaleIcon = + new SlidingLocaleDrawable(mContext, mSpacePreviewIcon, width, height); + mSlidingLocaleIcon.setBounds(0, 0, width, height); + mSpaceKey.iconPreview = mSlidingLocaleIcon; + } + mSlidingLocaleIcon.setDiff(diff); + if (Math.abs(diff) == Integer.MAX_VALUE) { + mSpaceKey.iconPreview = mSpacePreviewIcon; + } else { + mSpaceKey.iconPreview = mSlidingLocaleIcon; + } + mSpaceKey.iconPreview.invalidateSelf(); + } + + public int getLanguageChangeDirection() { + if (mSpaceKey == null || SubtypeSwitcher.getInstance().getEnabledKeyboardLocaleCount() <= 1 + || Math.abs(mSpaceDragLastDiff) < mSpaceKey.width * SPACEBAR_DRAG_THRESHOLD) { + return 0; // No change + } + return mSpaceDragLastDiff > 0 ? 1 : -1; + } + + boolean isCurrentlyInSpace() { + return mCurrentlyInSpace; + } + + public void setPreferredLetters(int[] frequencies) { + mPrefLetterFrequencies = frequencies; + mPrefLetter = 0; + } + + public void keyReleased() { + mCurrentlyInSpace = false; + mSpaceDragLastDiff = 0; + mPrefLetter = 0; + mPrefLetterX = 0; + mPrefLetterY = 0; + mPrefDistance = Integer.MAX_VALUE; + if (mSpaceKey != null) { + updateLocaleDrag(Integer.MAX_VALUE); + } + } + + /** + * Does the magic of locking the touch gesture into the spacebar when + * switching input languages. + */ + @SuppressWarnings("unused") + public boolean isInside(LatinKey key, int x, int y) { + final int code = key.codes[0]; + if (code == KEYCODE_SHIFT || code == KEYCODE_DELETE) { + y -= key.height / 10; + if (code == KEYCODE_SHIFT) x += key.width / 6; + if (code == KEYCODE_DELETE) x -= key.width / 6; + } else if (code == LatinIME.KEYCODE_SPACE) { + y += LatinKeyboard.sSpacebarVerticalCorrection; + if (SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCHER + && SubtypeSwitcher.getInstance().getEnabledKeyboardLocaleCount() > 1) { + if (mCurrentlyInSpace) { + int diff = x - mSpaceDragStartX; + if (Math.abs(diff - mSpaceDragLastDiff) > 0) { + updateLocaleDrag(diff); + } + mSpaceDragLastDiff = diff; + return true; + } else { + boolean insideSpace = key.isInsideSuper(x, y); + if (insideSpace) { + mCurrentlyInSpace = true; + mSpaceDragStartX = x; + updateLocaleDrag(0); + } + return insideSpace; + } + } + } else if (mPrefLetterFrequencies != null) { + // New coordinate? Reset + if (mPrefLetterX != x || mPrefLetterY != y) { + mPrefLetter = 0; + mPrefDistance = Integer.MAX_VALUE; + } + // Handle preferred next letter + final int[] pref = mPrefLetterFrequencies; + if (mPrefLetter > 0) { + if (DEBUG_PREFERRED_LETTER) { + if (mPrefLetter == code && !key.isInsideSuper(x, y)) { + Log.d(TAG, "CORRECTED !!!!!!"); + } + } + return mPrefLetter == code; + } else { + final boolean inside = key.isInsideSuper(x, y); + int[] nearby = getNearestKeys(x, y); + List nearbyKeys = getKeys(); + if (inside) { + // If it's a preferred letter + if (inPrefList(code, pref)) { + // Check if its frequency is much lower than a nearby key + mPrefLetter = code; + mPrefLetterX = x; + mPrefLetterY = y; + for (int i = 0; i < nearby.length; i++) { + Key k = nearbyKeys.get(nearby[i]); + if (k != key && inPrefList(k.codes[0], pref)) { + final int dist = distanceFrom(k, x, y); + if (dist < (int) (k.width * OVERLAP_PERCENTAGE_LOW_PROB) && + (pref[k.codes[0]] > pref[mPrefLetter] * 3)) { + mPrefLetter = k.codes[0]; + mPrefDistance = dist; + if (DEBUG_PREFERRED_LETTER) { + Log.d(TAG, "CORRECTED ALTHOUGH PREFERRED !!!!!!"); + } + break; + } + } + } + + return mPrefLetter == code; + } + } + + // Get the surrounding keys and intersect with the preferred list + // For all in the intersection + // if distance from touch point is within a reasonable distance + // make this the pref letter + // If no pref letter + // return inside; + // else return thiskey == prefletter; + + for (int i = 0; i < nearby.length; i++) { + Key k = nearbyKeys.get(nearby[i]); + if (inPrefList(k.codes[0], pref)) { + final int dist = distanceFrom(k, x, y); + if (dist < (int) (k.width * OVERLAP_PERCENTAGE_HIGH_PROB) + && dist < mPrefDistance) { + mPrefLetter = k.codes[0]; + mPrefLetterX = x; + mPrefLetterY = y; + mPrefDistance = dist; + } + } + } + // Didn't find any + if (mPrefLetter == 0) { + return inside; + } else { + return mPrefLetter == code; + } + } + } + + // Lock into the spacebar + if (mCurrentlyInSpace) return false; + + return key.isInsideSuper(x, y); + } + + private boolean inPrefList(int code, int[] pref) { + if (code < pref.length && code >= 0) return pref[code] > 0; + return false; + } + + private int distanceFrom(Key k, int x, int y) { + if (y > k.y && y < k.y + k.height) { + return Math.abs(k.x + k.width / 2 - x); + } else { + return Integer.MAX_VALUE; + } + } + + @Override + public int[] getNearestKeys(int x, int y) { + if (mCurrentlyInSpace) { + return new int[] { mSpaceKeyIndex }; + } else { + // Avoid dead pixels at edges of the keyboard + return super.getNearestKeys(Math.max(0, Math.min(x, getMinWidth() - 1)), + Math.max(0, Math.min(y, getHeight() - 1))); + } + } + + private int indexOf(int code) { + List keys = getKeys(); + int count = keys.size(); + for (int i = 0; i < count; i++) { + if (keys.get(i).codes[0] == code) return i; + } + return -1; + } + + private int getTextSizeFromTheme(int style, int defValue) { + TypedArray array = mContext.getTheme().obtainStyledAttributes( + style, new int[] { android.R.attr.textSize }); + int textSize = array.getDimensionPixelSize(array.getResourceId(0, 0), defValue); + return textSize; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardShiftState.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardShiftState.java new file mode 100644 index 000000000..6d7842857 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardShiftState.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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 com.android.inputmethod.latin.KeyboardSwitcher; + +import android.util.Log; + +public class LatinKeyboardShiftState { + private static final String TAG = "LatinKeyboardShiftState"; + private static final boolean DEBUG = KeyboardSwitcher.DEBUG_STATE; + + private static final int NORMAL = 0; + private static final int MANUAL_SHIFTED = 1; + private static final int SHIFT_LOCKED = 2; + private static final int AUTO_SHIFTED = 3; + private static final int SHIFT_LOCK_SHIFTED = 4; + + private int mState = NORMAL; + + public boolean setShifted(boolean newShiftState) { + final int oldState = mState; + if (newShiftState) { + if (oldState == NORMAL || oldState == AUTO_SHIFTED) { + mState = MANUAL_SHIFTED; + } else if (oldState == SHIFT_LOCKED) { + mState = SHIFT_LOCK_SHIFTED; + } + } else { + if (oldState == MANUAL_SHIFTED || oldState == AUTO_SHIFTED) { + mState = NORMAL; + } else if (oldState == SHIFT_LOCK_SHIFTED) { + mState = SHIFT_LOCKED; + } + } + if (DEBUG) + Log.d(TAG, "setShifted(" + newShiftState + "): " + toString(oldState) + " > " + this); + return mState != oldState; + } + + public void setShiftLocked(boolean newShiftLockState) { + final int oldState = mState; + if (newShiftLockState) { + if (oldState == NORMAL || oldState == MANUAL_SHIFTED || oldState == AUTO_SHIFTED) + mState = SHIFT_LOCKED; + } else { + if (oldState == SHIFT_LOCKED || oldState == SHIFT_LOCK_SHIFTED) + mState = NORMAL; + } + if (DEBUG) + Log.d(TAG, "setShiftLocked(" + newShiftLockState + "): " + toString(oldState) + + " > " + this); + } + + public void setAutomaticTemporaryUpperCase() { + final int oldState = mState; + mState = AUTO_SHIFTED; + if (DEBUG) + Log.d(TAG, "setAutomaticTemporaryUpperCase: " + toString(oldState) + " > " + this); + } + + public boolean isShiftedOrShiftLocked() { + return mState != NORMAL; + } + + public boolean isShiftLocked() { + return mState == SHIFT_LOCKED || mState == SHIFT_LOCK_SHIFTED; + } + + public boolean isAutomaticTemporaryUpperCase() { + return mState == AUTO_SHIFTED; + } + + public boolean isManualTemporaryUpperCase() { + return mState == MANUAL_SHIFTED || mState == SHIFT_LOCK_SHIFTED; + } + + @Override + public String toString() { + return toString(mState); + } + + private static String toString(int state) { + switch (state) { + case NORMAL: return "NORMAL"; + case MANUAL_SHIFTED: return "MANUAL_SHIFTED"; + case SHIFT_LOCKED: return "SHIFT_LOCKED"; + case AUTO_SHIFTED: return "AUTO_SHIFTED"; + case SHIFT_LOCK_SHIFTED: return "SHIFT_LOCK_SHIFTED"; + default: return "UKNOWN"; + } + } +} diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java new file mode 100644 index 000000000..55427d23f --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2008 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 com.android.inputmethod.latin.LatinIMEUtil; +import com.android.inputmethod.voice.VoiceIMEConnector; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import java.util.List; + +public class LatinKeyboardView extends KeyboardView { + + /** Whether we've started dropping move events because we found a big jump */ + private boolean mDroppingEvents; + /** + * Whether multi-touch disambiguation needs to be disabled if a real multi-touch event has + * occured + */ + private boolean mDisableDisambiguation; + /** The distance threshold at which we start treating the touch session as a multi-touch */ + private int mJumpThresholdSquare = Integer.MAX_VALUE; + /** The y coordinate of the last row */ + private int mLastRowY; + + public LatinKeyboardView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public void setPreviewEnabled(boolean previewEnabled) { + LatinKeyboard latinKeyboard = getLatinKeyboard(); + if (latinKeyboard != null + && (latinKeyboard.isPhoneKeyboard() || latinKeyboard.isNumberKeyboard())) { + // Phone and number keyboard never shows popup preview (except language switch). + super.setPreviewEnabled(false); + } else { + super.setPreviewEnabled(previewEnabled); + } + } + + public void setLatinKeyboard(LatinKeyboard k) { + super.setKeyboard(k); + // One-seventh of the keyboard width seems like a reasonable threshold + mJumpThresholdSquare = k.getMinWidth() / 7; + mJumpThresholdSquare *= mJumpThresholdSquare; + // Assuming there are 4 rows, this is the coordinate of the last row + mLastRowY = (k.getHeight() * 3) / 4; + setKeyboardLocal(k); + } + + public LatinKeyboard getLatinKeyboard() { + Keyboard keyboard = getKeyboard(); + if (keyboard instanceof LatinKeyboard) { + return (LatinKeyboard)keyboard; + } else { + return null; + } + } + + @Override + protected boolean onLongPress(Key key) { + int primaryCode = key.codes[0]; + if (primaryCode == LatinKeyboard.KEYCODE_OPTIONS) { + return invokeOnKey(LatinKeyboard.KEYCODE_OPTIONS_LONGPRESS); + } else if (primaryCode == '0' && getLatinKeyboard().isPhoneKeyboard()) { + // Long pressing on 0 in phone number keypad gives you a '+'. + return invokeOnKey('+'); + } else { + return super.onLongPress(key); + } + } + + private boolean invokeOnKey(int primaryCode) { + getOnKeyboardActionListener().onKey(primaryCode, null, + KeyboardView.NOT_A_TOUCH_COORDINATE, + KeyboardView.NOT_A_TOUCH_COORDINATE); + return true; + } + + @Override + protected CharSequence adjustCase(CharSequence label) { + LatinKeyboard keyboard = getLatinKeyboard(); + if (keyboard.isAlphaKeyboard() + && keyboard.isShiftedOrShiftLocked() + && !TextUtils.isEmpty(label) && label.length() < 3 + && Character.isLowerCase(label.charAt(0))) { + label = label.toString().toUpperCase(); + } + return label; + } + + /** + * This function checks to see if we need to handle any sudden jumps in the pointer location + * that could be due to a multi-touch being treated as a move by the firmware or hardware. + * Once a sudden jump is detected, all subsequent move events are discarded + * until an UP is received.

+ * When a sudden jump is detected, an UP event is simulated at the last position and when + * the sudden moves subside, a DOWN event is simulated for the second key. + * @param me the motion event + * @return true if the event was consumed, so that it doesn't continue to be handled by + * KeyboardView. + */ + private boolean handleSuddenJump(MotionEvent me) { + final int action = me.getAction(); + final int x = (int) me.getX(); + final int y = (int) me.getY(); + boolean result = false; + + // Real multi-touch event? Stop looking for sudden jumps + if (me.getPointerCount() > 1) { + mDisableDisambiguation = true; + } + if (mDisableDisambiguation) { + // If UP, reset the multi-touch flag + if (action == MotionEvent.ACTION_UP) mDisableDisambiguation = false; + return false; + } + + switch (action) { + case MotionEvent.ACTION_DOWN: + // Reset the "session" + mDroppingEvents = false; + mDisableDisambiguation = false; + break; + case MotionEvent.ACTION_MOVE: + // Is this a big jump? + final int distanceSquare = (mLastX - x) * (mLastX - x) + (mLastY - y) * (mLastY - y); + // Check the distance and also if the move is not entirely within the bottom row + // If it's only in the bottom row, it might be an intentional slide gesture + // for language switching + if (distanceSquare > mJumpThresholdSquare + && (mLastY < mLastRowY || y < mLastRowY)) { + // If we're not yet dropping events, start dropping and send an UP event + if (!mDroppingEvents) { + mDroppingEvents = true; + // Send an up event + MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(), + MotionEvent.ACTION_UP, + mLastX, mLastY, me.getMetaState()); + super.onTouchEvent(translated); + translated.recycle(); + } + result = true; + } else if (mDroppingEvents) { + // If moves are small and we're already dropping events, continue dropping + result = true; + } + break; + case MotionEvent.ACTION_UP: + if (mDroppingEvents) { + // Send a down event first, as we dropped a bunch of sudden jumps and assume that + // the user is releasing the touch on the second key. + MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(), + MotionEvent.ACTION_DOWN, + x, y, me.getMetaState()); + super.onTouchEvent(translated); + translated.recycle(); + mDroppingEvents = false; + // Let the up event get processed as well, result = false + } + break; + } + // Track the previous coordinate + mLastX = x; + mLastY = y; + return result; + } + + @Override + public boolean onTouchEvent(MotionEvent me) { + LatinKeyboard keyboard = getLatinKeyboard(); + if (DEBUG_LINE) { + mLastX = (int) me.getX(); + mLastY = (int) me.getY(); + invalidate(); + } + + // If there was a sudden jump, return without processing the actual motion event. + if (handleSuddenJump(me)) + return true; + + // Reset any bounding box controls in the keyboard + if (me.getAction() == MotionEvent.ACTION_DOWN) { + keyboard.keyReleased(); + } + + if (me.getAction() == MotionEvent.ACTION_UP) { + int languageDirection = keyboard.getLanguageChangeDirection(); + if (languageDirection != 0) { + getOnKeyboardActionListener().onKey( + languageDirection == 1 + ? LatinKeyboard.KEYCODE_NEXT_LANGUAGE : LatinKeyboard.KEYCODE_PREV_LANGUAGE, + null, mLastX, mLastY); + me.setAction(MotionEvent.ACTION_CANCEL); + keyboard.keyReleased(); + return super.onTouchEvent(me); + } + } + + return super.onTouchEvent(me); + } + + /**************************** INSTRUMENTATION *******************************/ + + public static final boolean DEBUG_AUTO_PLAY = false; + public static final boolean DEBUG_LINE = false; + private static final int MSG_TOUCH_DOWN = 1; + private static final int MSG_TOUCH_UP = 2; + + Handler mHandler2; + + private String mStringToPlay; + private int mStringIndex; + private boolean mDownDelivered; + private Key[] mAsciiKeys = new Key[256]; + private boolean mPlaying; + private int mLastX; + private int mLastY; + private Paint mPaint; + + private void setKeyboardLocal(LatinKeyboard k) { + if (DEBUG_AUTO_PLAY) { + findKeys(); + if (mHandler2 == null) { + mHandler2 = new Handler() { + @Override + public void handleMessage(Message msg) { + removeMessages(MSG_TOUCH_DOWN); + removeMessages(MSG_TOUCH_UP); + if (mPlaying == false) return; + + switch (msg.what) { + case MSG_TOUCH_DOWN: + if (mStringIndex >= mStringToPlay.length()) { + mPlaying = false; + return; + } + char c = mStringToPlay.charAt(mStringIndex); + while (c > 255 || mAsciiKeys[c] == null) { + mStringIndex++; + if (mStringIndex >= mStringToPlay.length()) { + mPlaying = false; + return; + } + c = mStringToPlay.charAt(mStringIndex); + } + int x = mAsciiKeys[c].x + 10; + int y = mAsciiKeys[c].y + 26; + MotionEvent me = MotionEvent.obtain(SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_DOWN, x, y, 0); + LatinKeyboardView.this.dispatchTouchEvent(me); + me.recycle(); + sendEmptyMessageDelayed(MSG_TOUCH_UP, 500); // Deliver up in 500ms if nothing else + // happens + mDownDelivered = true; + break; + case MSG_TOUCH_UP: + char cUp = mStringToPlay.charAt(mStringIndex); + int x2 = mAsciiKeys[cUp].x + 10; + int y2 = mAsciiKeys[cUp].y + 26; + mStringIndex++; + + MotionEvent me2 = MotionEvent.obtain(SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_UP, x2, y2, 0); + LatinKeyboardView.this.dispatchTouchEvent(me2); + me2.recycle(); + sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 500); // Deliver up in 500ms if nothing else + // happens + mDownDelivered = false; + break; + } + } + }; + + } + } + } + + private void findKeys() { + List keys = getLatinKeyboard().getKeys(); + // Get the keys on this keyboard + for (int i = 0; i < keys.size(); i++) { + int code = keys.get(i).codes[0]; + if (code >= 0 && code <= 255) { + mAsciiKeys[code] = keys.get(i); + } + } + } + + public void startPlaying(String s) { + if (DEBUG_AUTO_PLAY) { + if (s == null) return; + mStringToPlay = s.toLowerCase(); + mPlaying = true; + mDownDelivered = false; + mStringIndex = 0; + mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 10); + } + } + + @Override + public void draw(Canvas c) { + LatinIMEUtil.GCUtils.getInstance().reset(); + boolean tryGC = true; + for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { + try { + super.draw(c); + tryGC = false; + } catch (OutOfMemoryError e) { + tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait("LatinKeyboardView", e); + } + } + if (DEBUG_AUTO_PLAY) { + if (mPlaying) { + mHandler2.removeMessages(MSG_TOUCH_DOWN); + mHandler2.removeMessages(MSG_TOUCH_UP); + if (mDownDelivered) { + mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_UP, 20); + } else { + mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 20); + } + } + } + if (DEBUG_LINE) { + if (mPaint == null) { + mPaint = new Paint(); + mPaint.setColor(0x80FFFFFF); + mPaint.setAntiAlias(false); + } + c.drawLine(mLastX, 0, mLastX, getHeight(), mPaint); + c.drawLine(0, mLastY, getWidth(), mLastY, mPaint); + } + } + + @Override + protected void onAttachedToWindow() { + // Token is available from here. + VoiceIMEConnector.getInstance().onAttachedToWindow(); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java new file mode 100644 index 000000000..53879cde1 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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; + +public class MiniKeyboardKeyDetector extends KeyDetector { + private static final int MAX_NEARBY_KEYS = 1; + + private final int mSlideAllowanceSquare; + private final int mSlideAllowanceSquareTop; + + public MiniKeyboardKeyDetector(float slideAllowance) { + super(); + mSlideAllowanceSquare = (int)(slideAllowance * slideAllowance); + // Top slide allowance is slightly longer (sqrt(2) times) than other edges. + mSlideAllowanceSquareTop = mSlideAllowanceSquare * 2; + } + + @Override + protected int getMaxNearbyKeys() { + return MAX_NEARBY_KEYS; + } + + @Override + public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) { + final Key[] keys = getKeys(); + final int touchX = getTouchX(x); + final int touchY = getTouchY(y); + + int closestKeyIndex = NOT_A_KEY; + int closestKeyDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare; + final int keyCount = keys.length; + for (int index = 0; index < keyCount; index++) { + final int dist = keys[index].squaredDistanceToEdge(touchX, touchY); + if (dist < closestKeyDist) { + closestKeyIndex = index; + closestKeyDist = dist; + } + } + + if (allKeys != null && closestKeyIndex != NOT_A_KEY) + allKeys[0] = keys[closestKeyIndex].codes[0]; + return closestKeyIndex; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java new file mode 100644 index 000000000..aa0f9bd37 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -0,0 +1,546 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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 com.android.inputmethod.keyboard.KeyboardView.UIHandler; +import com.android.inputmethod.latin.LatinIME; +import com.android.inputmethod.latin.R; + +import android.content.res.Resources; +import android.util.Log; +import android.view.MotionEvent; + +public class PointerTracker { + private static final String TAG = "PointerTracker"; + private static final boolean DEBUG = false; + private static final boolean DEBUG_MOVE = false; + + public interface UIProxy { + public void invalidateKey(Key key); + public void showPreview(int keyIndex, PointerTracker tracker); + public boolean hasDistinctMultitouch(); + } + + public final int mPointerId; + + // Timing constants + private final int mDelayBeforeKeyRepeatStart; + private final int mLongPressKeyTimeout; + private final int mLongPressShiftKeyTimeout; + private final int mMultiTapKeyTimeout; + + // Miscellaneous constants + private static final int NOT_A_KEY = KeyDetector.NOT_A_KEY; + private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE }; + + private final UIProxy mProxy; + private final UIHandler mHandler; + private final KeyDetector mKeyDetector; + private KeyboardActionListener mListener; + private final boolean mHasDistinctMultitouch; + + private Keyboard mKeyboard; + private Key[] mKeys; + private int mKeyHysteresisDistanceSquared = -1; + + private final KeyState mKeyState; + + // true if event is already translated to a key action (long press or mini-keyboard) + private boolean mKeyAlreadyProcessed; + + // true if this pointer is repeatable key + private boolean mIsRepeatableKey; + + // For multi-tap + private int mLastSentIndex; + private int mTapCount; + private long mLastTapTime; + private boolean mInMultiTap; + private final StringBuilder mPreviewLabel = new StringBuilder(1); + + // pressed key + private int mPreviousKey = NOT_A_KEY; + + // This class keeps track of a key index and a position where this pointer is. + private static class KeyState { + private final KeyDetector mKeyDetector; + + // The position and time at which first down event occurred. + private int mStartX; + private int mStartY; + private long mDownTime; + + // The current key index where this pointer is. + private int mKeyIndex = NOT_A_KEY; + // The position where mKeyIndex was recognized for the first time. + private int mKeyX; + private int mKeyY; + + // Last pointer position. + private int mLastX; + private int mLastY; + + public KeyState(KeyDetector keyDetecor) { + mKeyDetector = keyDetecor; + } + + public int getKeyIndex() { + return mKeyIndex; + } + + public int getKeyX() { + return mKeyX; + } + + public int getKeyY() { + return mKeyY; + } + + public int getStartX() { + return mStartX; + } + + public int getStartY() { + return mStartY; + } + + public long getDownTime() { + return mDownTime; + } + + public int getLastX() { + return mLastX; + } + + public int getLastY() { + return mLastY; + } + + public int onDownKey(int x, int y, long eventTime) { + mStartX = x; + mStartY = y; + mDownTime = eventTime; + + return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); + } + + private int onMoveKeyInternal(int x, int y) { + mLastX = x; + mLastY = y; + return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); + } + + public int onMoveKey(int x, int y) { + return onMoveKeyInternal(x, y); + } + + public int onMoveToNewKey(int keyIndex, int x, int y) { + mKeyIndex = keyIndex; + mKeyX = x; + mKeyY = y; + return keyIndex; + } + + public int onUpKey(int x, int y) { + return onMoveKeyInternal(x, y); + } + + public void onSetKeyboard() { + mKeyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(mKeyX, mKeyY, null); + } + } + + public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector, UIProxy proxy, + Resources res) { + if (proxy == null || handler == null || keyDetector == null) + throw new NullPointerException(); + mPointerId = id; + mProxy = proxy; + mHandler = handler; + mKeyDetector = keyDetector; + mKeyState = new KeyState(keyDetector); + mHasDistinctMultitouch = proxy.hasDistinctMultitouch(); + mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start); + mLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout); + mLongPressShiftKeyTimeout = res.getInteger(R.integer.config_long_press_shift_key_timeout); + mMultiTapKeyTimeout = res.getInteger(R.integer.config_multi_tap_key_timeout); + resetMultiTap(); + } + + public void setOnKeyboardActionListener(KeyboardActionListener listener) { + mListener = listener; + } + + public void setKeyboard(Keyboard keyboard, Key[] keys, float keyHysteresisDistance) { + if (keyboard == null || keys == null || keyHysteresisDistance < 0) + throw new IllegalArgumentException(); + mKeyboard = keyboard; + mKeys = keys; + mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance); + // Update current key index because keyboard layout has been changed. + mKeyState.onSetKeyboard(); + } + + private boolean isValidKeyIndex(int keyIndex) { + return keyIndex >= 0 && keyIndex < mKeys.length; + } + + public Key getKey(int keyIndex) { + return isValidKeyIndex(keyIndex) ? mKeys[keyIndex] : null; + } + + private boolean isModifierInternal(int keyIndex) { + Key key = getKey(keyIndex); + if (key == null) + return false; + int primaryCode = key.codes[0]; + return primaryCode == Keyboard.KEYCODE_SHIFT + || primaryCode == Keyboard.KEYCODE_MODE_CHANGE; + } + + public boolean isModifier() { + return isModifierInternal(mKeyState.getKeyIndex()); + } + + public boolean isOnModifierKey(int x, int y) { + return isModifierInternal(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null)); + } + + public boolean isSpaceKey(int keyIndex) { + Key key = getKey(keyIndex); + return key != null && key.codes[0] == LatinIME.KEYCODE_SPACE; + } + + public void releaseKey() { + updateKeyGraphics(NOT_A_KEY); + } + + private void updateKeyGraphics(int keyIndex) { + int oldKeyIndex = mPreviousKey; + mPreviousKey = keyIndex; + if (keyIndex != oldKeyIndex) { + if (isValidKeyIndex(oldKeyIndex)) { + // if new key index is not a key, old key was just released inside of the key. + final boolean inside = (keyIndex == NOT_A_KEY); + mKeys[oldKeyIndex].onReleased(inside); + mProxy.invalidateKey(mKeys[oldKeyIndex]); + } + if (isValidKeyIndex(keyIndex)) { + mKeys[keyIndex].onPressed(); + mProxy.invalidateKey(mKeys[keyIndex]); + } + } + } + + public void setAlreadyProcessed() { + mKeyAlreadyProcessed = true; + } + + public void onTouchEvent(int action, int x, int y, long eventTime) { + switch (action) { + case MotionEvent.ACTION_MOVE: + onMoveEvent(x, y, eventTime); + break; + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + onDownEvent(x, y, eventTime); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + onUpEvent(x, y, eventTime); + break; + case MotionEvent.ACTION_CANCEL: + onCancelEvent(x, y, eventTime); + break; + } + } + + public void onDownEvent(int x, int y, long eventTime) { + if (DEBUG) + debugLog("onDownEvent:", x, y); + int keyIndex = mKeyState.onDownKey(x, y, eventTime); + mKeyAlreadyProcessed = false; + mIsRepeatableKey = false; + checkMultiTap(eventTime, keyIndex); + if (mListener != null) { + if (isValidKeyIndex(keyIndex)) { + mListener.onPress(mKeys[keyIndex].codes[0]); + // This onPress call may have changed keyboard layout and have updated mKeyIndex. + // If that's the case, mKeyIndex has been updated in setKeyboard(). + keyIndex = mKeyState.getKeyIndex(); + } + } + if (isValidKeyIndex(keyIndex)) { + if (mKeys[keyIndex].repeatable) { + repeatKey(keyIndex); + mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this); + mIsRepeatableKey = true; + } + startLongPressTimer(keyIndex); + } + showKeyPreviewAndUpdateKeyGraphics(keyIndex); + } + + public void onMoveEvent(int x, int y, long eventTime) { + if (DEBUG_MOVE) + debugLog("onMoveEvent:", x, y); + if (mKeyAlreadyProcessed) + return; + KeyState keyState = mKeyState; + final int keyIndex = keyState.onMoveKey(x, y); + final Key oldKey = getKey(keyState.getKeyIndex()); + if (isValidKeyIndex(keyIndex)) { + if (oldKey == null) { + keyState.onMoveToNewKey(keyIndex, x, y); + startLongPressTimer(keyIndex); + } else if (!isMinorMoveBounce(x, y, keyIndex)) { + if (mListener != null) + mListener.onRelease(oldKey.codes[0]); + resetMultiTap(); + keyState.onMoveToNewKey(keyIndex, x, y); + startLongPressTimer(keyIndex); + } + } else { + if (oldKey != null) { + if (mListener != null) + mListener.onRelease(oldKey.codes[0]); + keyState.onMoveToNewKey(keyIndex, x ,y); + mHandler.cancelLongPressTimers(); + } else if (!isMinorMoveBounce(x, y, keyIndex)) { + resetMultiTap(); + keyState.onMoveToNewKey(keyIndex, x ,y); + mHandler.cancelLongPressTimers(); + } + } + showKeyPreviewAndUpdateKeyGraphics(mKeyState.getKeyIndex()); + } + + public void onUpEvent(int x, int y, long eventTime) { + if (DEBUG) + debugLog("onUpEvent :", x, y); + showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY); + if (mKeyAlreadyProcessed) + return; + mHandler.cancelKeyTimers(); + mHandler.cancelPopupPreview(); + int keyIndex = mKeyState.onUpKey(x, y); + if (isMinorMoveBounce(x, y, keyIndex)) { + // Use previous fixed key index and coordinates. + keyIndex = mKeyState.getKeyIndex(); + x = mKeyState.getKeyX(); + y = mKeyState.getKeyY(); + } + if (!mIsRepeatableKey) { + detectAndSendKey(keyIndex, x, y, eventTime); + } + + if (isValidKeyIndex(keyIndex)) + mProxy.invalidateKey(mKeys[keyIndex]); + } + + public void onCancelEvent(int x, int y, long eventTime) { + if (DEBUG) + debugLog("onCancelEvt:", x, y); + mHandler.cancelKeyTimers(); + mHandler.cancelPopupPreview(); + showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY); + int keyIndex = mKeyState.getKeyIndex(); + if (isValidKeyIndex(keyIndex)) + mProxy.invalidateKey(mKeys[keyIndex]); + } + + public void repeatKey(int keyIndex) { + Key key = getKey(keyIndex); + if (key != null) { + // While key is repeating, because there is no need to handle multi-tap key, we can + // pass -1 as eventTime argument. + detectAndSendKey(keyIndex, key.x, key.y, -1); + } + } + + public int getLastX() { + return mKeyState.getLastX(); + } + + public int getLastY() { + return mKeyState.getLastY(); + } + + public long getDownTime() { + return mKeyState.getDownTime(); + } + + // These package scope methods are only for debugging purpose. + /* package */ int getStartX() { + return mKeyState.getStartX(); + } + + /* package */ int getStartY() { + return mKeyState.getStartY(); + } + + private boolean isMinorMoveBounce(int x, int y, int newKey) { + if (mKeys == null || mKeyHysteresisDistanceSquared < 0) + throw new IllegalStateException("keyboard and/or hysteresis not set"); + int curKey = mKeyState.getKeyIndex(); + if (newKey == curKey) { + return true; + } else if (isValidKeyIndex(curKey)) { + return mKeys[curKey].squaredDistanceToEdge(x, y) < mKeyHysteresisDistanceSquared; + } else { + return false; + } + } + + private void showKeyPreviewAndUpdateKeyGraphics(int keyIndex) { + updateKeyGraphics(keyIndex); + // The modifier key, such as shift key, should not be shown as preview when multi-touch is + // supported. On the other hand, if multi-touch is not supported, the modifier key should + // be shown as preview. + if (mHasDistinctMultitouch && isModifier()) { + mProxy.showPreview(NOT_A_KEY, this); + } else { + mProxy.showPreview(keyIndex, this); + } + } + + private void startLongPressTimer(int keyIndex) { + Key key = getKey(keyIndex); + if (key.codes[0] == Keyboard.KEYCODE_SHIFT) { + mHandler.startLongPressShiftTimer(mLongPressShiftKeyTimeout, keyIndex, this); + } else { + mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this); + } + } + + private boolean isManualTemporaryUpperCase() { + return mKeyboard instanceof LatinKeyboard + && ((LatinKeyboard)mKeyboard).isManualTemporaryUpperCase(); + } + + private void detectAndSendKey(int index, int x, int y, long eventTime) { + final KeyboardActionListener listener = mListener; + final Key key = getKey(index); + + if (key == null) { + if (listener != null) + listener.onCancel(); + } else { + if (key.text != null) { + if (listener != null) { + listener.onText(key.text); + listener.onRelease(NOT_A_KEY); + } + } else { + int code = key.codes[0]; + //TextEntryState.keyPressedAt(key, x, y); + int[] codes = mKeyDetector.newCodeArray(); + mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes); + // Multi-tap + if (mInMultiTap) { + if (mTapCount != -1) { + mListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE, x, y); + } else { + mTapCount = 0; + } + code = key.codes[mTapCount]; + } + + // If keyboard is in manual temporary upper case state and key has manual temporary + // shift code, alternate character code should be sent. + if (isManualTemporaryUpperCase() && key.manualTemporaryUpperCaseCode != 0) { + code = key.manualTemporaryUpperCaseCode; + codes[0] = code; + } + + /* + * Swap the first and second values in the codes array if the primary code is not + * the first value but the second value in the array. This happens when key + * debouncing is in effect. + */ + if (codes.length >= 2 && codes[0] != code && codes[1] == code) { + codes[1] = codes[0]; + codes[0] = code; + } + if (listener != null) { + listener.onKey(code, codes, x, y); + listener.onRelease(code); + } + } + mLastSentIndex = index; + mLastTapTime = eventTime; + } + } + + /** + * Handle multi-tap keys by producing the key label for the current multi-tap state. + */ + public CharSequence getPreviewText(Key key) { + if (mInMultiTap) { + // Multi-tap + mPreviewLabel.setLength(0); + mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]); + return mPreviewLabel; + } else { + return key.label; + } + } + + private void resetMultiTap() { + mLastSentIndex = NOT_A_KEY; + mTapCount = 0; + mLastTapTime = -1; + mInMultiTap = false; + } + + private void checkMultiTap(long eventTime, int keyIndex) { + Key key = getKey(keyIndex); + if (key == null) + return; + + final boolean isMultiTap = + (eventTime < mLastTapTime + mMultiTapKeyTimeout && keyIndex == mLastSentIndex); + if (key.codes.length > 1) { + mInMultiTap = true; + if (isMultiTap) { + mTapCount = (mTapCount + 1) % key.codes.length; + return; + } else { + mTapCount = -1; + return; + } + } + if (!isMultiTap) { + resetMultiTap(); + } + } + + private void debugLog(String title, int x, int y) { + int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); + Key key = getKey(keyIndex); + final String code; + if (key == null) { + code = "----"; + } else { + int primaryCode = key.codes[0]; + code = String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode); + } + Log.d(TAG, String.format("%s%s[%d] %3d,%3d %3d(%s) %s", title, + (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, keyIndex, code, + (isModifier() ? "modifier" : ""))); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java new file mode 100644 index 000000000..a3c080828 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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 java.util.LinkedList; + +public class PointerTrackerQueue { + private LinkedList mQueue = new LinkedList(); + + public void add(PointerTracker tracker) { + mQueue.add(tracker); + } + + public int lastIndexOf(PointerTracker tracker) { + LinkedList queue = mQueue; + for (int index = queue.size() - 1; index >= 0; index--) { + PointerTracker t = queue.get(index); + if (t == tracker) + return index; + } + return -1; + } + + public void releaseAllPointersOlderThan(PointerTracker tracker, long eventTime) { + LinkedList queue = mQueue; + int oldestPos = 0; + for (PointerTracker t = queue.get(oldestPos); t != tracker; t = queue.get(oldestPos)) { + if (t.isModifier()) { + oldestPos++; + } else { + t.onUpEvent(t.getLastX(), t.getLastY(), eventTime); + t.setAlreadyProcessed(); + queue.remove(oldestPos); + } + } + } + + public void releaseAllPointersExcept(PointerTracker tracker, long eventTime) { + for (PointerTracker t : mQueue) { + if (t == tracker) + continue; + t.onUpEvent(t.getLastX(), t.getLastY(), eventTime); + t.setAlreadyProcessed(); + } + mQueue.clear(); + if (tracker != null) + mQueue.add(tracker); + } + + public void remove(PointerTracker tracker) { + mQueue.remove(tracker); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java b/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java new file mode 100644 index 000000000..047173445 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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 java.util.Arrays; + +public class ProximityKeyDetector extends KeyDetector { + private static final int MAX_NEARBY_KEYS = 12; + + // working area + private int[] mDistances = new int[MAX_NEARBY_KEYS]; + + @Override + protected int getMaxNearbyKeys() { + return MAX_NEARBY_KEYS; + } + + @Override + public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) { + final Key[] keys = getKeys(); + final int touchX = getTouchX(x); + final int touchY = getTouchY(y); + + int primaryIndex = NOT_A_KEY; + int closestKeyIndex = NOT_A_KEY; + int closestKeyDist = mProximityThresholdSquare + 1; + final int[] distances = mDistances; + Arrays.fill(distances, Integer.MAX_VALUE); + for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) { + final Key key = keys[index]; + final boolean isInside = key.isInside(touchX, touchY); + if (isInside) + primaryIndex = index; + final int dist = key.squaredDistanceToEdge(touchX, touchY); + if (isInside || (mProximityCorrectOn && dist < mProximityThresholdSquare)) { + if (dist < closestKeyDist) { + closestKeyDist = dist; + closestKeyIndex = index; + } + + if (allKeys == null) continue; + final int nCodes = key.codes.length; + // Find insertion point + for (int j = 0; j < distances.length; j++) { + if (distances[j] > dist) { + // Make space for nCodes codes + System.arraycopy(distances, j, distances, j + nCodes, + distances.length - (j + nCodes)); + System.arraycopy(allKeys, j, allKeys, j + nCodes, + allKeys.length - (j + nCodes)); + System.arraycopy(key.codes, 0, allKeys, j, nCodes); + Arrays.fill(distances, j, j + nCodes, dist); + break; + } + } + } + } + + return primaryIndex == NOT_A_KEY ? closestKeyIndex : primaryIndex; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/Row.java b/java/src/com/android/inputmethod/keyboard/Row.java new file mode 100644 index 000000000..cb778f317 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/Row.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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 com.android.inputmethod.latin.R; + +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.util.Xml; + +/** + * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate. + * Some of the key size defaults can be overridden per row from what the {@link Keyboard} + * defines. + */ +public class Row { + /** Default width of a key in this row. */ + public int defaultWidth; + /** Default height of a key in this row. */ + public int defaultHeight; + /** Default horizontal gap between keys in this row. */ + public int defaultHorizontalGap; + /** Vertical gap following this row. */ + public int verticalGap; + /** + * Edge flags for this row of keys. Possible values that can be assigned are + * {@link Keyboard#EDGE_TOP EDGE_TOP} and {@link Keyboard#EDGE_BOTTOM EDGE_BOTTOM} + */ + public int rowEdgeFlags; + + final Keyboard parent; + + Row(Keyboard parent) { + this.parent = parent; + } + + public Row(Resources res, Keyboard parent, XmlResourceParser parser) { + this.parent = parent; + TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard); + defaultWidth = KeyboardParser.getDimensionOrFraction(a, + R.styleable.Keyboard_keyWidth, + parent.mDisplayWidth, parent.mDefaultWidth); + defaultHeight = KeyboardParser.getDimensionOrFraction(a, + R.styleable.Keyboard_keyHeight, + parent.mDisplayHeight, parent.mDefaultHeight); + defaultHorizontalGap = KeyboardParser.getDimensionOrFraction(a, + R.styleable.Keyboard_horizontalGap, + parent.mDisplayWidth, parent.mDefaultHorizontalGap); + verticalGap = KeyboardParser.getDimensionOrFraction(a, + R.styleable.Keyboard_verticalGap, + parent.mDisplayHeight, parent.mDefaultVerticalGap); + a.recycle(); + a = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard_Row); + rowEdgeFlags = a.getInt(R.styleable.Keyboard_Row_rowEdgeFlags, 0); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/SlidingLocaleDrawable.java b/java/src/com/android/inputmethod/keyboard/SlidingLocaleDrawable.java new file mode 100644 index 000000000..689791cff --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/SlidingLocaleDrawable.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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 com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.SubtypeSwitcher; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Paint.Align; +import android.graphics.drawable.Drawable; +import android.text.TextPaint; +import android.view.ViewConfiguration; + +/** + * Animation to be displayed on the spacebar preview popup when switching languages by swiping the + * spacebar. It draws the current, previous and next languages and moves them by the delta of touch + * movement on the spacebar. + */ +public class SlidingLocaleDrawable extends Drawable { + + private final Context mContext; + private final Resources mRes; + private final int mWidth; + private final int mHeight; + private final Drawable mBackground; + private final TextPaint mTextPaint; + private final int mMiddleX; + private final Drawable mLeftDrawable; + private final Drawable mRightDrawable; + private final int mThreshold; + private int mDiff; + private boolean mHitThreshold; + private String mCurrentLanguage; + private String mNextLanguage; + private String mPrevLanguage; + + public SlidingLocaleDrawable(Context context, Drawable background, int width, int height) { + mContext = context; + mRes = context.getResources(); + mBackground = background; + LatinKeyboard.setDefaultBounds(mBackground); + mWidth = width; + mHeight = height; + final TextPaint textPaint = new TextPaint(); + textPaint.setTextSize(getTextSizeFromTheme(android.R.style.TextAppearance_Medium, 18)); + textPaint.setColor(R.color.latinkeyboard_transparent); + textPaint.setTextAlign(Align.CENTER); + textPaint.setAlpha(LatinKeyboard.OPACITY_FULLY_OPAQUE); + textPaint.setAntiAlias(true); + mTextPaint = textPaint; + mMiddleX = (mWidth - mBackground.getIntrinsicWidth()) / 2; + final Resources res = mRes; + mLeftDrawable = res.getDrawable( + R.drawable.sym_keyboard_feedback_language_arrows_left); + mRightDrawable = res.getDrawable( + R.drawable.sym_keyboard_feedback_language_arrows_right); + mThreshold = ViewConfiguration.get(mContext).getScaledTouchSlop(); + } + + private int getTextSizeFromTheme(int style, int defValue) { + TypedArray array = mContext.getTheme().obtainStyledAttributes( + style, new int[] { android.R.attr.textSize }); + int textSize = array.getDimensionPixelSize(array.getResourceId(0, 0), defValue); + return textSize; + } + + void setDiff(int diff) { + if (diff == Integer.MAX_VALUE) { + mHitThreshold = false; + mCurrentLanguage = null; + return; + } + mDiff = diff; + if (mDiff > mWidth) mDiff = mWidth; + if (mDiff < -mWidth) mDiff = -mWidth; + if (Math.abs(mDiff) > mThreshold) mHitThreshold = true; + invalidateSelf(); + } + + + @Override + public void draw(Canvas canvas) { + canvas.save(); + if (mHitThreshold) { + Paint paint = mTextPaint; + final int width = mWidth; + final int height = mHeight; + final int diff = mDiff; + final Drawable lArrow = mLeftDrawable; + final Drawable rArrow = mRightDrawable; + canvas.clipRect(0, 0, width, height); + if (mCurrentLanguage == null) { + SubtypeSwitcher subtypeSwitcher = SubtypeSwitcher.getInstance(); + mCurrentLanguage = subtypeSwitcher.getInputLanguageName(); + mNextLanguage = subtypeSwitcher.getNextInputLanguageName(); + mPrevLanguage = subtypeSwitcher.getPreviousInputLanguageName(); + } + // Draw language text with shadow + final float baseline = mHeight * LatinKeyboard.SPACEBAR_LANGUAGE_BASELINE + - paint.descent(); + paint.setColor(mRes.getColor(R.color.latinkeyboard_feedback_language_text)); + canvas.drawText(mCurrentLanguage, width / 2 + diff, baseline, paint); + canvas.drawText(mNextLanguage, diff - width / 2, baseline, paint); + canvas.drawText(mPrevLanguage, diff + width + width / 2, baseline, paint); + + LatinKeyboard.setDefaultBounds(lArrow); + rArrow.setBounds(width - rArrow.getIntrinsicWidth(), 0, width, + rArrow.getIntrinsicHeight()); + lArrow.draw(canvas); + rArrow.draw(canvas); + } + if (mBackground != null) { + canvas.translate(mMiddleX, 0); + mBackground.draw(canvas); + } + canvas.restore(); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public void setAlpha(int alpha) { + // Ignore + } + + @Override + public void setColorFilter(ColorFilter cf) { + // Ignore + } + + @Override + public int getIntrinsicWidth() { + return mWidth; + } + + @Override + public int getIntrinsicHeight() { + return mHeight; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/SwipeTracker.java b/java/src/com/android/inputmethod/keyboard/SwipeTracker.java new file mode 100644 index 000000000..730cdc390 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/SwipeTracker.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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.view.MotionEvent; + +public class SwipeTracker { + private static final int NUM_PAST = 4; + private static final int LONGEST_PAST_TIME = 200; + + final EventRingBuffer mBuffer = new EventRingBuffer(NUM_PAST); + + private float mYVelocity; + private float mXVelocity; + + public void addMovement(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mBuffer.clear(); + return; + } + long time = ev.getEventTime(); + final int count = ev.getHistorySize(); + for (int i = 0; i < count; i++) { + addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i), ev.getHistoricalEventTime(i)); + } + addPoint(ev.getX(), ev.getY(), time); + } + + private void addPoint(float x, float y, long time) { + final EventRingBuffer buffer = mBuffer; + while (buffer.size() > 0) { + long lastT = buffer.getTime(0); + if (lastT >= time - LONGEST_PAST_TIME) + break; + buffer.dropOldest(); + } + buffer.add(x, y, time); + } + + public void computeCurrentVelocity(int units) { + computeCurrentVelocity(units, Float.MAX_VALUE); + } + + public void computeCurrentVelocity(int units, float maxVelocity) { + final EventRingBuffer buffer = mBuffer; + final float oldestX = buffer.getX(0); + final float oldestY = buffer.getY(0); + final long oldestTime = buffer.getTime(0); + + float accumX = 0; + float accumY = 0; + final int count = buffer.size(); + for (int pos = 1; pos < count; pos++) { + final int dur = (int)(buffer.getTime(pos) - oldestTime); + if (dur == 0) continue; + float dist = buffer.getX(pos) - oldestX; + float vel = (dist / dur) * units; // pixels/frame. + if (accumX == 0) accumX = vel; + else accumX = (accumX + vel) * .5f; + + dist = buffer.getY(pos) - oldestY; + vel = (dist / dur) * units; // pixels/frame. + if (accumY == 0) accumY = vel; + else accumY = (accumY + vel) * .5f; + } + mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) + : Math.min(accumX, maxVelocity); + mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) + : Math.min(accumY, maxVelocity); + } + + public float getXVelocity() { + return mXVelocity; + } + + public float getYVelocity() { + return mYVelocity; + } + + public static class EventRingBuffer { + private final int bufSize; + private final float xBuf[]; + private final float yBuf[]; + private final long timeBuf[]; + private int top; // points new event + private int end; // points oldest event + private int count; // the number of valid data + + public EventRingBuffer(int max) { + this.bufSize = max; + xBuf = new float[max]; + yBuf = new float[max]; + timeBuf = new long[max]; + clear(); + } + + public void clear() { + top = end = count = 0; + } + + public int size() { + return count; + } + + // Position 0 points oldest event + private int index(int pos) { + return (end + pos) % bufSize; + } + + private int advance(int index) { + return (index + 1) % bufSize; + } + + public void add(float x, float y, long time) { + xBuf[top] = x; + yBuf[top] = y; + timeBuf[top] = time; + top = advance(top); + if (count < bufSize) { + count++; + } else { + end = advance(end); + } + } + + public float getX(int pos) { + return xBuf[index(pos)]; + } + + public float getY(int pos) { + return yBuf[index(pos)]; + } + + public long getTime(int pos) { + return timeBuf[index(pos)]; + } + + public void dropOldest() { + count--; + end = advance(end); + } + } +} diff --git a/java/src/com/android/inputmethod/latin/BaseKeyboard.java b/java/src/com/android/inputmethod/latin/BaseKeyboard.java deleted file mode 100644 index e2331f334..000000000 --- a/java/src/com/android/inputmethod/latin/BaseKeyboard.java +++ /dev/null @@ -1,717 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * 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.latin; - -import com.android.inputmethod.latin.BaseKeyboardParser.ParseException; -import com.android.inputmethod.latin.KeyStyles.KeyStyle; -import com.android.inputmethod.latin.KeyboardSwitcher.KeyboardId; - -import org.xmlpull.v1.XmlPullParserException; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.graphics.drawable.Drawable; -import android.text.TextUtils; -import android.util.Log; -import android.util.Xml; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard - * consists of rows of keys. - *

The layout file for a keyboard contains XML that looks like the following snippet:

- *
- * <Keyboard
- *         latin:keyWidth="%10p"
- *         latin:keyHeight="50px"
- *         latin:horizontalGap="2px"
- *         latin:verticalGap="2px" >
- *     <Row latin:keyWidth="32px" >
- *         <Key latin:keyLabel="A" />
- *         ...
- *     </Row>
- *     ...
- * </Keyboard>
- * 
- */ -public class BaseKeyboard { - - static final String TAG = "BaseKeyboard"; - - public static final int EDGE_LEFT = 0x01; - public static final int EDGE_RIGHT = 0x02; - public static final int EDGE_TOP = 0x04; - public static final int EDGE_BOTTOM = 0x08; - - public static final int KEYCODE_SHIFT = -1; - public static final int KEYCODE_MODE_CHANGE = -2; - public static final int KEYCODE_CANCEL = -3; - public static final int KEYCODE_DONE = -4; - public static final int KEYCODE_DELETE = -5; - public static final int KEYCODE_ALT = -6; - - /** Horizontal gap default for all rows */ - private int mDefaultHorizontalGap; - - /** Default key width */ - private int mDefaultWidth; - - /** Default key height */ - private int mDefaultHeight; - - /** Default gap between rows */ - private int mDefaultVerticalGap; - - /** Is the keyboard in the shifted state */ - private boolean mShifted; - - /** List of shift keys in this keyboard */ - private final List mShiftKeys = new ArrayList(); - - /** List of shift keys and its shifted state icon */ - private final HashMap mShiftedIcons = new HashMap(); - - /** Total height of the keyboard, including the padding and keys */ - private int mTotalHeight; - - /** - * Total width of the keyboard, including left side gaps and keys, but not any gaps on the - * right side. - */ - private int mTotalWidth; - - /** List of keys in this keyboard */ - private final List mKeys = new ArrayList(); - - /** Width of the screen available to fit the keyboard */ - private final int mDisplayWidth; - - /** Height of the screen */ - private final int mDisplayHeight; - - protected final KeyboardId mId; - - // Variables for pre-computing nearest keys. - - public final int GRID_WIDTH; - public final int GRID_HEIGHT; - private final int GRID_SIZE; - private int mCellWidth; - private int mCellHeight; - private int[][] mGridNeighbors; - private int mProximityThreshold; - private static int[] EMPTY_INT_ARRAY = new int[0]; - /** Number of key widths from current touch point to search for nearest keys. */ - private static float SEARCH_DISTANCE = 1.2f; - - /** - * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate. - * Some of the key size defaults can be overridden per row from what the {@link BaseKeyboard} - * defines. - */ - public static class Row { - /** Default width of a key in this row. */ - public int defaultWidth; - /** Default height of a key in this row. */ - public int defaultHeight; - /** Default horizontal gap between keys in this row. */ - public int defaultHorizontalGap; - /** Vertical gap following this row. */ - public int verticalGap; - /** - * Edge flags for this row of keys. Possible values that can be assigned are - * {@link BaseKeyboard#EDGE_TOP EDGE_TOP} and {@link BaseKeyboard#EDGE_BOTTOM EDGE_BOTTOM} - */ - public int rowEdgeFlags; - - private final BaseKeyboard parent; - - private Row(BaseKeyboard parent) { - this.parent = parent; - } - - public Row(Resources res, BaseKeyboard parent, XmlResourceParser parser) { - this.parent = parent; - TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.BaseKeyboard); - defaultWidth = BaseKeyboardParser.getDimensionOrFraction(a, - R.styleable.BaseKeyboard_keyWidth, - parent.mDisplayWidth, parent.mDefaultWidth); - defaultHeight = BaseKeyboardParser.getDimensionOrFraction(a, - R.styleable.BaseKeyboard_keyHeight, - parent.mDisplayHeight, parent.mDefaultHeight); - defaultHorizontalGap = BaseKeyboardParser.getDimensionOrFraction(a, - R.styleable.BaseKeyboard_horizontalGap, - parent.mDisplayWidth, parent.mDefaultHorizontalGap); - verticalGap = BaseKeyboardParser.getDimensionOrFraction(a, - R.styleable.BaseKeyboard_verticalGap, - parent.mDisplayHeight, parent.mDefaultVerticalGap); - a.recycle(); - a = res.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.BaseKeyboard_Row); - rowEdgeFlags = a.getInt(R.styleable.BaseKeyboard_Row_rowEdgeFlags, 0); - } - } - - /** - * Class for describing the position and characteristics of a single key in the keyboard. - */ - public static class Key { - /** - * All the key codes (unicode or custom code) that this key could generate, zero'th - * being the most important. - */ - public int[] codes; - /** The unicode that this key generates in manual temporary upper case mode. */ - public int manualTemporaryUpperCaseCode; - - /** Label to display */ - public CharSequence label; - /** Option of the label */ - public int labelOption; - - /** Icon to display instead of a label. Icon takes precedence over a label */ - public Drawable icon; - /** Hint icon to display on the key in conjunction with the label */ - public Drawable hintIcon; - /** Preview version of the icon, for the preview popup */ - /** - * The hint icon to display on the key when keyboard is in manual temporary upper case - * mode. - */ - public Drawable manualTemporaryUpperCaseHintIcon; - - public Drawable iconPreview; - /** Width of the key, not including the gap */ - public int width; - /** Height of the key, not including the gap */ - public int height; - /** The horizontal gap before this key */ - public int gap; - /** Whether this key is sticky, i.e., a toggle key */ - public boolean sticky; - /** X coordinate of the key in the keyboard layout */ - public int x; - /** Y coordinate of the key in the keyboard layout */ - public int y; - /** The current pressed state of this key */ - public boolean pressed; - /** If this is a sticky key, is it on? */ - public boolean on; - /** Text to output when pressed. This can be multiple characters, like ".com" */ - public CharSequence text; - /** Popup characters */ - public CharSequence popupCharacters; - - /** - * Flags that specify the anchoring to edges of the keyboard for detecting touch events - * that are just out of the boundary of the key. This is a bit mask of - * {@link BaseKeyboard#EDGE_LEFT}, {@link BaseKeyboard#EDGE_RIGHT}, - * {@link BaseKeyboard#EDGE_TOP} and {@link BaseKeyboard#EDGE_BOTTOM}. - */ - public int edgeFlags; - /** Whether this is a modifier key, such as Shift or Alt */ - public boolean modifier; - /** The BaseKeyboard that this key belongs to */ - protected final BaseKeyboard keyboard; - /** - * If this key pops up a mini keyboard, this is the resource id for the XML layout for that - * keyboard. - */ - public int popupResId; - /** Whether this key repeats itself when held down */ - public boolean repeatable; - - - private final static int[] KEY_STATE_NORMAL_ON = { - android.R.attr.state_checkable, - android.R.attr.state_checked - }; - - private final static int[] KEY_STATE_PRESSED_ON = { - android.R.attr.state_pressed, - android.R.attr.state_checkable, - android.R.attr.state_checked - }; - - private final static int[] KEY_STATE_NORMAL_OFF = { - android.R.attr.state_checkable - }; - - private final static int[] KEY_STATE_PRESSED_OFF = { - android.R.attr.state_pressed, - android.R.attr.state_checkable - }; - - private final static int[] KEY_STATE_NORMAL = { - }; - - private final static int[] KEY_STATE_PRESSED = { - android.R.attr.state_pressed - }; - - /** Create an empty key with no attributes. */ - public Key(Row parent) { - keyboard = parent.parent; - height = parent.defaultHeight; - gap = parent.defaultHorizontalGap; - width = parent.defaultWidth - gap; - edgeFlags = parent.rowEdgeFlags; - } - - /** Create a key with the given top-left coordinate and extract its attributes from - * the XML parser. - * @param res resources associated with the caller's context - * @param parent the row that this key belongs to. The row must already be attached to - * a {@link BaseKeyboard}. - * @param x the x coordinate of the top-left - * @param y the y coordinate of the top-left - * @param parser the XML parser containing the attributes for this key - */ - public Key(Resources res, Row parent, int x, int y, XmlResourceParser parser, - KeyStyles keyStyles) { - this(parent); - - TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.BaseKeyboard); - height = BaseKeyboardParser.getDimensionOrFraction(a, - R.styleable.BaseKeyboard_keyHeight, - keyboard.mDisplayHeight, parent.defaultHeight); - gap = BaseKeyboardParser.getDimensionOrFraction(a, - R.styleable.BaseKeyboard_horizontalGap, - keyboard.mDisplayWidth, parent.defaultHorizontalGap); - width = BaseKeyboardParser.getDimensionOrFraction(a, - R.styleable.BaseKeyboard_keyWidth, - keyboard.mDisplayWidth, parent.defaultWidth) - gap; - a.recycle(); - - a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.BaseKeyboard_Key); - - final KeyStyle style; - if (a.hasValue(R.styleable.BaseKeyboard_Key_keyStyle)) { - String styleName = a.getString(R.styleable.BaseKeyboard_Key_keyStyle); - style = keyStyles.getKeyStyle(styleName); - if (style == null) - throw new ParseException("Unknown key style: " + styleName, parser); - } else { - style = keyStyles.getEmptyKeyStyle(); - } - - // Horizontal gap is divided equally to both sides of the key. - this.x = x + gap / 2; - this.y = y; - - codes = style.getIntArray(a, R.styleable.BaseKeyboard_Key_codes); - iconPreview = style.getDrawable(a, R.styleable.BaseKeyboard_Key_iconPreview); - setDefaultBounds(iconPreview); - popupCharacters = style.getText(a, R.styleable.BaseKeyboard_Key_popupCharacters); - popupResId = style.getResourceId(a, R.styleable.BaseKeyboard_Key_popupKeyboard, 0); - repeatable = style.getBoolean(a, R.styleable.BaseKeyboard_Key_isRepeatable, false); - modifier = style.getBoolean(a, R.styleable.BaseKeyboard_Key_isModifier, false); - sticky = style.getBoolean(a, R.styleable.BaseKeyboard_Key_isSticky, false); - edgeFlags = style.getFlag(a, R.styleable.BaseKeyboard_Key_keyEdgeFlags, 0); - edgeFlags |= parent.rowEdgeFlags; - - icon = style.getDrawable(a, R.styleable.BaseKeyboard_Key_keyIcon); - setDefaultBounds(icon); - hintIcon = style.getDrawable(a, R.styleable.BaseKeyboard_Key_keyHintIcon); - setDefaultBounds(hintIcon); - manualTemporaryUpperCaseHintIcon = style.getDrawable(a, - R.styleable.BaseKeyboard_Key_manualTemporaryUpperCaseHintIcon); - setDefaultBounds(manualTemporaryUpperCaseHintIcon); - - label = style.getText(a, R.styleable.BaseKeyboard_Key_keyLabel); - labelOption = style.getFlag(a, R.styleable.BaseKeyboard_Key_keyLabelOption, 0); - manualTemporaryUpperCaseCode = style.getInt(a, - R.styleable.BaseKeyboard_Key_manualTemporaryUpperCaseCode, 0); - text = style.getText(a, R.styleable.BaseKeyboard_Key_keyOutputText); - final Drawable shiftedIcon = style.getDrawable(a, - R.styleable.BaseKeyboard_Key_shiftedIcon); - if (shiftedIcon != null) - keyboard.getShiftedIcons().put(this, shiftedIcon); - - if (codes == null && !TextUtils.isEmpty(label)) { - codes = new int[] { label.charAt(0) }; - } - a.recycle(); - } - - /** - * Informs the key that it has been pressed, in case it needs to change its appearance or - * state. - * @see #onReleased(boolean) - */ - public void onPressed() { - pressed = !pressed; - } - - /** - * Changes the pressed state of the key. If it is a sticky key, it will also change the - * toggled state of the key if the finger was release inside. - * @param inside whether the finger was released inside the key - * @see #onPressed() - */ - public void onReleased(boolean inside) { - pressed = !pressed; - if (sticky) { - on = !on; - } - } - - /** - * Detects if a point falls inside this key. - * @param x the x-coordinate of the point - * @param y the y-coordinate of the point - * @return whether or not the point falls inside the key. If the key is attached to an - * edge, it will assume that all points between the key and the edge are considered to be - * inside the key. - */ - public boolean isInside(int x, int y) { - boolean leftEdge = (edgeFlags & EDGE_LEFT) > 0; - boolean rightEdge = (edgeFlags & EDGE_RIGHT) > 0; - boolean topEdge = (edgeFlags & EDGE_TOP) > 0; - boolean bottomEdge = (edgeFlags & EDGE_BOTTOM) > 0; - if ((x >= this.x || (leftEdge && x <= this.x + this.width)) - && (x < this.x + this.width || (rightEdge && x >= this.x)) - && (y >= this.y || (topEdge && y <= this.y + this.height)) - && (y < this.y + this.height || (bottomEdge && y >= this.y))) { - return true; - } else { - return false; - } - } - - /** - * Returns the square of the distance to the nearest edge of the key and the given point. - * @param x the x-coordinate of the point - * @param y the y-coordinate of the point - * @return the square of the distance of the point from the nearest edge of the key - */ - public int squaredDistanceToEdge(int x, int y) { - final int left = this.x; - final int right = left + this.width; - final int top = this.y; - final int bottom = top + this.height; - final int edgeX = x < left ? left : (x > right ? right : x); - final int edgeY = y < top ? top : (y > bottom ? bottom : y); - final int dx = x - edgeX; - final int dy = y - edgeY; - return dx * dx + dy * dy; - } - - /** - * Returns the drawable state for the key, based on the current state and type of the key. - * @return the drawable state of the key. - * @see android.graphics.drawable.StateListDrawable#setState(int[]) - */ - public int[] getCurrentDrawableState() { - int[] states = KEY_STATE_NORMAL; - - if (on) { - if (pressed) { - states = KEY_STATE_PRESSED_ON; - } else { - states = KEY_STATE_NORMAL_ON; - } - } else { - if (sticky) { - if (pressed) { - states = KEY_STATE_PRESSED_OFF; - } else { - states = KEY_STATE_NORMAL_OFF; - } - } else { - if (pressed) { - states = KEY_STATE_PRESSED; - } - } - } - return states; - } - } - - /** - * Creates a keyboard from the given xml key layout file. - * @param context the application or service context - * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. - */ - public BaseKeyboard(Context context, int xmlLayoutResId) { - this(context, xmlLayoutResId, null); - } - - /** - * Creates a keyboard from the given keyboard identifier. - * @param context the application or service context - * @param id keyboard identifier - */ - public BaseKeyboard(Context context, KeyboardId id) { - this(context, id.getXmlId(), id); - } - - /** - * Creates a keyboard from the given xml key layout file. - * @param context the application or service context - * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. - * @param id keyboard identifier - */ - private BaseKeyboard(Context context, int xmlLayoutResId, KeyboardId id) { - this(context, xmlLayoutResId, id, - context.getResources().getDisplayMetrics().widthPixels, - context.getResources().getDisplayMetrics().heightPixels); - } - - private BaseKeyboard(Context context, int xmlLayoutResId, KeyboardId id, int width, - int height) { - Resources res = context.getResources(); - GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width); - GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height); - GRID_SIZE = GRID_WIDTH * GRID_HEIGHT; - - mDisplayWidth = width; - mDisplayHeight = height; - - mDefaultHorizontalGap = 0; - setKeyWidth(mDisplayWidth / 10); - mDefaultVerticalGap = 0; - mDefaultHeight = mDefaultWidth; - mId = id; - loadKeyboard(context, xmlLayoutResId); - } - - /** - *

Creates a blank keyboard from the given resource file and populates it with the specified - * characters in left-to-right, top-to-bottom fashion, using the specified number of columns. - *

- *

If the specified number of columns is -1, then the keyboard will fit as many keys as - * possible in each row.

- * @param context the application or service context - * @param layoutTemplateResId the layout template file, containing no keys. - * @param characters the list of characters to display on the keyboard. One key will be created - * for each character. - * @param columns the number of columns of keys to display. If this number is greater than the - * number of keys that can fit in a row, it will be ignored. If this number is -1, the - * keyboard will fit as many keys as possible in each row. - */ - public BaseKeyboard(Context context, int layoutTemplateResId, - CharSequence characters, int columns, int horizontalPadding) { - this(context, layoutTemplateResId); - int x = 0; - int y = 0; - int column = 0; - mTotalWidth = 0; - - Row row = new Row(this); - row.defaultHeight = mDefaultHeight; - row.defaultWidth = mDefaultWidth; - row.defaultHorizontalGap = mDefaultHorizontalGap; - row.verticalGap = mDefaultVerticalGap; - row.rowEdgeFlags = EDGE_TOP | EDGE_BOTTOM; - final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns; - for (int i = 0; i < characters.length(); i++) { - char c = characters.charAt(i); - if (column >= maxColumns - || x + mDefaultWidth + horizontalPadding > mDisplayWidth) { - x = 0; - y += mDefaultVerticalGap + mDefaultHeight; - column = 0; - } - final Key key = new Key(row); - // Horizontal gap is divided equally to both sides of the key. - key.x = x + key.gap / 2; - key.y = y; - key.label = String.valueOf(c); - key.codes = new int[] { c }; - column++; - x += key.width + key.gap; - mKeys.add(key); - if (x > mTotalWidth) { - mTotalWidth = x; - } - } - mTotalHeight = y + mDefaultHeight; - } - - public KeyboardId getKeyboardId() { - return mId; - } - - public List getKeys() { - return mKeys; - } - - protected int getHorizontalGap() { - return mDefaultHorizontalGap; - } - - protected void setHorizontalGap(int gap) { - mDefaultHorizontalGap = gap; - } - - protected int getVerticalGap() { - return mDefaultVerticalGap; - } - - protected void setVerticalGap(int gap) { - mDefaultVerticalGap = gap; - } - - protected int getKeyHeight() { - return mDefaultHeight; - } - - protected void setKeyHeight(int height) { - mDefaultHeight = height; - } - - protected int getKeyWidth() { - return mDefaultWidth; - } - - protected void setKeyWidth(int width) { - mDefaultWidth = width; - final int threshold = (int) (width * SEARCH_DISTANCE); - mProximityThreshold = threshold * threshold; - } - - /** - * Returns the total height of the keyboard - * @return the total height of the keyboard - */ - public int getHeight() { - return mTotalHeight; - } - - public int getMinWidth() { - return mTotalWidth; - } - - public int getKeyboardHeight() { - return mDisplayHeight; - } - - public int getKeyboardWidth() { - return mDisplayWidth; - } - - public boolean setShifted(boolean shiftState) { - for (final Key key : mShiftKeys) { - key.on = shiftState; - } - if (mShifted != shiftState) { - mShifted = shiftState; - return true; - } - return false; - } - - public boolean isShiftedOrShiftLocked() { - return mShifted; - } - - public List getShiftKeys() { - return mShiftKeys; - } - - public Map getShiftedIcons() { - return mShiftedIcons; - } - - private void computeNearestNeighbors() { - // Round-up so we don't have any pixels outside the grid - mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH; - mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT; - mGridNeighbors = new int[GRID_SIZE][]; - final int[] indices = new int[mKeys.size()]; - final int gridWidth = GRID_WIDTH * mCellWidth; - final int gridHeight = GRID_HEIGHT * mCellHeight; - final int threshold = mProximityThreshold; - for (int x = 0; x < gridWidth; x += mCellWidth) { - for (int y = 0; y < gridHeight; y += mCellHeight) { - final int centerX = x + mCellWidth / 2; - final int centerY = y + mCellHeight / 2; - int count = 0; - for (int i = 0; i < mKeys.size(); i++) { - final Key key = mKeys.get(i); - if (key.squaredDistanceToEdge(centerX, centerY) < threshold) - indices[count++] = i; - } - final int[] cell = new int[count]; - System.arraycopy(indices, 0, cell, 0, count); - mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell; - } - } - } - - /** - * Returns the indices of the keys that are closest to the given point. - * @param x the x-coordinate of the point - * @param y the y-coordinate of the point - * @return the array of integer indices for the nearest keys to the given point. If the given - * point is out of range, then an array of size zero is returned. - */ - public int[] getNearestKeys(int x, int y) { - if (mGridNeighbors == null) computeNearestNeighbors(); - if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) { - int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth); - if (index < GRID_SIZE) { - return mGridNeighbors[index]; - } - } - return EMPTY_INT_ARRAY; - } - - // TODO should be private - protected BaseKeyboard.Row createRowFromXml(Resources res, XmlResourceParser parser) { - return new BaseKeyboard.Row(res, this, parser); - } - - // TODO should be private - protected BaseKeyboard.Key createKeyFromXml(Resources res, Row parent, int x, int y, - XmlResourceParser parser, KeyStyles keyStyles) { - return new BaseKeyboard.Key(res, parent, x, y, parser, keyStyles); - } - - private void loadKeyboard(Context context, int xmlLayoutResId) { - try { - final Resources res = context.getResources(); - BaseKeyboardParser parser = new BaseKeyboardParser(this, res); - parser.parseKeyboard(res.getXml(xmlLayoutResId)); - // mTotalWidth is the width of this keyboard which is maximum width of row. - mTotalWidth = parser.getMaxRowWidth(); - mTotalHeight = parser.getTotalHeight(); - } catch (XmlPullParserException e) { - Log.w(TAG, "keyboard XML parse error: " + e); - throw new IllegalArgumentException(e); - } catch (IOException e) { - Log.w(TAG, "keyboard XML parse error: " + e); - throw new RuntimeException(e); - } - } - - protected static void setDefaultBounds(Drawable drawable) { - if (drawable != null) - drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), - drawable.getIntrinsicHeight()); - } -} diff --git a/java/src/com/android/inputmethod/latin/BaseKeyboardParser.java b/java/src/com/android/inputmethod/latin/BaseKeyboardParser.java deleted file mode 100644 index 38b2a1b57..000000000 --- a/java/src/com/android/inputmethod/latin/BaseKeyboardParser.java +++ /dev/null @@ -1,571 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * 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.latin; - -import com.android.inputmethod.latin.BaseKeyboard.Key; -import com.android.inputmethod.latin.BaseKeyboard.Row; -import com.android.inputmethod.latin.KeyboardSwitcher.KeyboardId; - -import org.xmlpull.v1.XmlPullParserException; - -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.util.Log; -import android.util.TypedValue; -import android.util.Xml; -import android.view.InflateException; - -import java.io.IOException; -import java.util.List; - -/** - * Parser for BaseKeyboard. - * - * This class parses Keyboard XML file and fill out keys in BaseKeyboard. - * The Keyboard XML file looks like: - *
- *   >!-- xml/keyboard.xml --<
- *   >Keyboard keyboard_attributes*<
- *     >!-- Keyboard Content --<
- *     >Row row_attributes*<
- *       >!-- Row Content --<
- *       >Key key_attributes* /<
- *       >Spacer horizontalGap="0.2in" /<
- *       >include keyboardLayout="@xml/other_keys"<
- *       ...
- *     >/Row<
- *     >include keyboardLayout="@xml/other_rows"<
- *     ...
- *   >/Keyboard<
- * 
- * The XML file which is included in other file must have >merge< as root element, such as: - *
- *   >!-- xml/other_keys.xml --<
- *   >merge<
- *     >Key key_attributes* /<
- *     ...
- *   >/merge<
- * 
- * and - *
- *   >!-- xml/other_rows.xml --<
- *   >merge<
- *     >Row row_attributes*<
- *       >Key key_attributes* /<
- *     >/Row<
- *     ...
- *   >/merge<
- * 
- * You can also use switch-case-default tags to select Rows and Keys. - *
- *   >switch<
- *     >case case_attribute*<
- *       >!-- Any valid tags at switch position --<
- *     >/case<
- *     ...
- *     >default<
- *       >!-- Any valid tags at switch position --<
- *     >/default<
- *   >/switch<
- * 
- * You can declare Key style and specify styles within Key tags. - *
- *     >switch<
- *       >case colorScheme="white"<
- *         >key-style styleName="shift-key" parentStyle="modifier-key"
- *           keyIcon="@drawable/sym_keyboard_shift"
- *         /<
- *       >/case<
- *       >case colorScheme="black"<
- *         >key-style styleName="shift-key" parentStyle="modifier-key"
- *           keyIcon="@drawable/sym_bkeyboard_shift"
- *         /<
- *       >/case<
- *     >/switch<
- *     ...
- *     >Key keyStyle="shift-key" ... /<
- * 
- */ - -public class BaseKeyboardParser { - private static final String TAG = "BaseKeyboardParser"; - private static final boolean DEBUG_TAG = false; - - // Keyboard XML Tags - private static final String TAG_KEYBOARD = "Keyboard"; - private static final String TAG_ROW = "Row"; - private static final String TAG_KEY = "Key"; - private static final String TAG_SPACER = "Spacer"; - private static final String TAG_INCLUDE = "include"; - private static final String TAG_MERGE = "merge"; - private static final String TAG_SWITCH = "switch"; - private static final String TAG_CASE = "case"; - private static final String TAG_DEFAULT = "default"; - private static final String TAG_KEY_STYLE = "key-style"; - - private final BaseKeyboard mKeyboard; - private final Resources mResources; - - private int mCurrentX = 0; - private int mCurrentY = 0; - private int mMaxRowWidth = 0; - private int mTotalHeight = 0; - private Row mCurrentRow = null; - private final KeyStyles mKeyStyles = new KeyStyles(); - - public BaseKeyboardParser(BaseKeyboard keyboard, Resources res) { - mKeyboard = keyboard; - mResources = res; - } - - public int getMaxRowWidth() { - return mMaxRowWidth; - } - - public int getTotalHeight() { - return mTotalHeight; - } - - public void parseKeyboard(XmlResourceParser parser) - throws XmlPullParserException, IOException { - int event; - while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { - if (event == XmlResourceParser.START_TAG) { - final String tag = parser.getName(); - if (DEBUG_TAG) debugStartTag("parseKeyboard", tag, false); - if (TAG_KEYBOARD.equals(tag)) { - parseKeyboardAttributes(parser); - parseKeyboardContent(parser, mKeyboard.getKeys()); - break; - } else { - throw new IllegalStartTag(parser, TAG_KEYBOARD); - } - } - } - } - - private void parseKeyboardAttributes(XmlResourceParser parser) { - final BaseKeyboard keyboard = mKeyboard; - final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.BaseKeyboard); - final int width = keyboard.getKeyboardWidth(); - final int height = keyboard.getKeyboardHeight(); - keyboard.setKeyWidth(getDimensionOrFraction(a, - R.styleable.BaseKeyboard_keyWidth, width, width / 10)); - keyboard.setKeyHeight(getDimensionOrFraction(a, - R.styleable.BaseKeyboard_keyHeight, height, 50)); - keyboard.setHorizontalGap(getDimensionOrFraction(a, - R.styleable.BaseKeyboard_horizontalGap, width, 0)); - keyboard.setVerticalGap(getDimensionOrFraction(a, - R.styleable.BaseKeyboard_verticalGap, height, 0)); - a.recycle(); - if (DEBUG_TAG) Log.d(TAG, "id=" + keyboard.mId); - } - - private void parseKeyboardContent(XmlResourceParser parser, List keys) - throws XmlPullParserException, IOException { - int event; - while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { - if (event == XmlResourceParser.START_TAG) { - final String tag = parser.getName(); - if (DEBUG_TAG) debugStartTag("parseKeyboardContent", tag, keys == null); - if (TAG_ROW.equals(tag)) { - Row row = mKeyboard.createRowFromXml(mResources, parser); - if (keys != null) - startRow(row); - parseRowContent(parser, row, keys); - } else if (TAG_INCLUDE.equals(tag)) { - parseIncludeKeyboardContent(parser, keys); - } else if (TAG_SWITCH.equals(tag)) { - parseSwitchKeyboardContent(parser, keys); - } else if (TAG_KEY_STYLE.equals(tag)) { - parseKeyStyle(parser, keys); - } else { - throw new IllegalStartTag(parser, TAG_ROW); - } - } else if (event == XmlResourceParser.END_TAG) { - final String tag = parser.getName(); - if (DEBUG_TAG) debugEndTag("parseKeyboardContent", tag, keys == null); - if (TAG_KEYBOARD.equals(tag)) { - endKeyboard(mKeyboard.getVerticalGap()); - break; - } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)) { - break; - } else if (TAG_MERGE.equals(tag)) { - break; - } else if (TAG_KEY_STYLE.equals(tag)) { - continue; - } else { - throw new IllegalEndTag(parser, TAG_ROW); - } - } - } - } - - private void parseRowContent(XmlResourceParser parser, Row row, List keys) - throws XmlPullParserException, IOException { - int event; - while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { - if (event == XmlResourceParser.START_TAG) { - final String tag = parser.getName(); - if (DEBUG_TAG) debugStartTag("parseRowContent", tag, keys == null); - if (TAG_KEY.equals(tag)) { - parseKey(parser, row, keys); - } else if (TAG_SPACER.equals(tag)) { - parseSpacer(parser, keys); - } else if (TAG_INCLUDE.equals(tag)) { - parseIncludeRowContent(parser, row, keys); - } else if (TAG_SWITCH.equals(tag)) { - parseSwitchRowContent(parser, row, keys); - } else if (TAG_KEY_STYLE.equals(tag)) { - parseKeyStyle(parser, keys); - } else { - throw new IllegalStartTag(parser, TAG_KEY); - } - } else if (event == XmlResourceParser.END_TAG) { - final String tag = parser.getName(); - if (DEBUG_TAG) debugEndTag("parseRowContent", tag, keys == null); - if (TAG_ROW.equals(tag)) { - if (keys != null) - endRow(); - break; - } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)) { - break; - } else if (TAG_MERGE.equals(tag)) { - break; - } else if (TAG_KEY_STYLE.equals(tag)) { - continue; - } else { - throw new IllegalEndTag(parser, TAG_KEY); - } - } - } - } - - private void parseKey(XmlResourceParser parser, Row row, List keys) - throws XmlPullParserException, IOException { - if (keys == null) { - checkEndTag(TAG_KEY, parser); - } else { - Key key = mKeyboard.createKeyFromXml(mResources, row, mCurrentX, mCurrentY, parser, - mKeyStyles); - checkEndTag(TAG_KEY, parser); - keys.add(key); - if (key.codes[0] == BaseKeyboard.KEYCODE_SHIFT) - mKeyboard.getShiftKeys().add(key); - endKey(key); - } - } - - private void parseSpacer(XmlResourceParser parser, List keys) - throws XmlPullParserException, IOException { - if (keys == null) { - checkEndTag(TAG_SPACER, parser); - } else { - final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.BaseKeyboard); - final int gap = getDimensionOrFraction(a, R.styleable.BaseKeyboard_horizontalGap, - mKeyboard.getKeyboardWidth(), 0); - a.recycle(); - checkEndTag(TAG_SPACER, parser); - setSpacer(gap); - } - } - - private void parseIncludeKeyboardContent(XmlResourceParser parser, List keys) - throws XmlPullParserException, IOException { - parseIncludeInternal(parser, null, keys); - } - - private void parseIncludeRowContent(XmlResourceParser parser, Row row, List keys) - throws XmlPullParserException, IOException { - parseIncludeInternal(parser, row, keys); - } - - private void parseIncludeInternal(XmlResourceParser parser, Row row, List keys) - throws XmlPullParserException, IOException { - if (keys == null) { - checkEndTag(TAG_INCLUDE, parser); - } else { - final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.BaseKeyboard_Include); - final int keyboardLayout = a.getResourceId( - R.styleable.BaseKeyboard_Include_keyboardLayout, 0); - a.recycle(); - - checkEndTag(TAG_INCLUDE, parser); - if (keyboardLayout == 0) - throw new ParseException("No keyboardLayout attribute in ", parser); - if (DEBUG_TAG) Log.d(TAG, String.format(" keyboardLayout=0x%08x", keyboardLayout)); - parseMerge(mResources.getLayout(keyboardLayout), row, keys); - } - } - - private void parseMerge(XmlResourceParser parser, Row row, List keys) - throws XmlPullParserException, IOException { - int event; - while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { - if (event == XmlResourceParser.START_TAG) { - final String tag = parser.getName(); - if (DEBUG_TAG) debugStartTag("parseMerge", tag, keys == null); - if (TAG_MERGE.equals(tag)) { - if (row == null) { - parseKeyboardContent(parser, keys); - } else { - parseRowContent(parser, row, keys); - } - break; - } else { - throw new ParseException( - "Included keyboard layout must have root element", parser); - } - } - } - } - - private void parseSwitchKeyboardContent(XmlResourceParser parser, List keys) - throws XmlPullParserException, IOException { - parseSwitchInternal(parser, null, keys); - } - - private void parseSwitchRowContent(XmlResourceParser parser, Row row, List keys) - throws XmlPullParserException, IOException { - parseSwitchInternal(parser, row, keys); - } - - private void parseSwitchInternal(XmlResourceParser parser, Row row, List keys) - throws XmlPullParserException, IOException { - boolean selected = false; - int event; - if (DEBUG_TAG) Log.d(TAG, "parseSwitchInternal: id=" + mKeyboard.mId); - while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { - if (event == XmlResourceParser.START_TAG) { - final String tag = parser.getName(); - if (DEBUG_TAG) debugStartTag("parseSwitchInternal", tag, keys == null); - if (TAG_CASE.equals(tag)) { - selected |= parseCase(parser, row, selected ? null : keys); - } else if (TAG_DEFAULT.equals(tag)) { - selected |= parseDefault(parser, row, selected ? null : keys); - } else { - throw new IllegalStartTag(parser, TAG_KEY); - } - } else if (event == XmlResourceParser.END_TAG) { - final String tag = parser.getName(); - if (DEBUG_TAG) debugEndTag("parseRowContent", tag, keys == null); - if (TAG_SWITCH.equals(tag)) { - break; - } else { - throw new IllegalEndTag(parser, TAG_KEY); - } - } - } - } - - private boolean parseCase(XmlResourceParser parser, Row row, List keys) - throws XmlPullParserException, IOException { - final boolean selected = parseCaseCondition(parser); - if (row == null) { - // Processing Rows. - parseKeyboardContent(parser, selected ? keys : null); - } else { - // Processing Keys. - parseRowContent(parser, row, selected ? keys : null); - } - return selected; - } - - private boolean parseCaseCondition(XmlResourceParser parser) { - final KeyboardId id = mKeyboard.mId; - if (id == null) - return true; - - final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.BaseKeyboard_Case); - final TypedArray viewAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.BaseKeyboardView); - try { - final boolean modeMatched = matchInteger(a, - R.styleable.BaseKeyboard_Case_mode, id.mMode); - final boolean settingsKeyMatched = matchBoolean(a, - R.styleable.BaseKeyboard_Case_hasSettingsKey, id.mHasSettingsKey); - final boolean voiceEnabledMatched = matchBoolean(a, - R.styleable.BaseKeyboard_Case_voiceKeyEnabled, id.mVoiceKeyEnabled); - final boolean voiceKeyMatched = matchBoolean(a, - R.styleable.BaseKeyboard_Case_hasVoiceKey, id.mHasVoiceKey); - final boolean colorSchemeMatched = matchInteger(viewAttr, - R.styleable.BaseKeyboardView_colorScheme, id.mColorScheme); - // As noted at KeyboardSwitcher.KeyboardId class, we are interested only in - // enum value masked by IME_MASK_ACTION and IME_FLAG_NO_ENTER_ACTION. So matching - // this attribute with id.mImeOptions as integer value is enough for our purpose. - final boolean imeOptionsMatched = matchInteger(a, - R.styleable.BaseKeyboard_Case_imeOptions, id.mImeOptions); - final boolean selected = modeMatched && settingsKeyMatched && voiceEnabledMatched - && voiceKeyMatched && colorSchemeMatched && imeOptionsMatched; - - if (DEBUG_TAG) { - Log.d(TAG, String.format( - "parseCaseCondition: %s%s%s%s%s%s%s", - Boolean.toString(selected).toUpperCase(), - debugInteger(a, R.styleable.BaseKeyboard_Case_mode, "mode"), - debugBoolean(a, R.styleable.BaseKeyboard_Case_hasSettingsKey, - "hasSettingsKey"), - debugBoolean(a, R.styleable.BaseKeyboard_Case_voiceKeyEnabled, - "voiceKeyEnabled"), - debugBoolean(a, R.styleable.BaseKeyboard_Case_hasVoiceKey, "hasVoiceKey"), - debugInteger(viewAttr, R.styleable.BaseKeyboardView_colorScheme, - "colorScheme"), - debugInteger(a, R.styleable.BaseKeyboard_Case_imeOptions, "imeOptions"))); - } - - return selected; - } finally { - a.recycle(); - viewAttr.recycle(); - } - } - - private static boolean matchInteger(TypedArray a, int index, int value) { - // If does not have "index" attribute, that means this is wild-card for the - // attribute. - return !a.hasValue(index) || a.getInt(index, 0) == value; - } - - private static boolean matchBoolean(TypedArray a, int index, boolean value) { - // If does not have "index" attribute, that means this is wild-card for the - // attribute. - return !a.hasValue(index) || a.getBoolean(index, false) == value; - } - - private boolean parseDefault(XmlResourceParser parser, Row row, List keys) - throws XmlPullParserException, IOException { - if (row == null) { - parseKeyboardContent(parser, keys); - } else { - parseRowContent(parser, row, keys); - } - return true; - } - - private void parseKeyStyle(XmlResourceParser parser, List keys) - throws XmlPullParserException, IOException { - TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.BaseKeyboard_KeyStyle); - TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.BaseKeyboard_Key); - try { - if (!a.hasValue(R.styleable.BaseKeyboard_KeyStyle_styleName)) - throw new ParseException("<" + TAG_KEY_STYLE - + "/> needs styleName attribute", parser); - if (keys != null) - mKeyStyles.parseKeyStyleAttributes(a, keyAttrs, parser); - } finally { - a.recycle(); - keyAttrs.recycle(); - } - } - - private static void checkEndTag(String tag, XmlResourceParser parser) - throws XmlPullParserException, IOException { - if (parser.next() == XmlResourceParser.END_TAG && tag.equals(parser.getName())) - return; - throw new NonEmptyTag(tag, parser); - } - - private void startRow(Row row) { - mCurrentX = 0; - mCurrentRow = row; - } - - private void endRow() { - if (mCurrentRow == null) - throw new InflateException("orphant end row tag"); - mCurrentY += mCurrentRow.verticalGap + mCurrentRow.defaultHeight; - mCurrentRow = null; - } - - private void endKey(Key key) { - mCurrentX += key.gap + key.width; - if (mCurrentX > mMaxRowWidth) - mMaxRowWidth = mCurrentX; - } - - private void endKeyboard(int defaultVerticalGap) { - mTotalHeight = mCurrentY - defaultVerticalGap; - } - - private void setSpacer(int gap) { - mCurrentX += gap; - } - - public static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) { - final TypedValue value = a.peekValue(index); - if (value == null) - return defValue; - if (value.type == TypedValue.TYPE_DIMENSION) { - return a.getDimensionPixelOffset(index, defValue); - } else if (value.type == TypedValue.TYPE_FRACTION) { - // Round it to avoid values like 47.9999 from getting truncated - return Math.round(a.getFraction(index, base, base, defValue)); - } - return defValue; - } - - @SuppressWarnings("serial") - public static class ParseException extends InflateException { - public ParseException(String msg, XmlResourceParser parser) { - super(msg + " at line " + parser.getLineNumber()); - } - } - - @SuppressWarnings("serial") - private static class IllegalStartTag extends ParseException { - public IllegalStartTag(XmlResourceParser parser, String parent) { - super("Illegal start tag " + parser.getName() + " in " + parent, parser); - } - } - - @SuppressWarnings("serial") - private static class IllegalEndTag extends ParseException { - public IllegalEndTag(XmlResourceParser parser, String parent) { - super("Illegal end tag " + parser.getName() + " in " + parent, parser); - } - } - - @SuppressWarnings("serial") - private static class NonEmptyTag extends ParseException { - public NonEmptyTag(String tag, XmlResourceParser parser) { - super(tag + " must be empty tag", parser); - } - } - - private static void debugStartTag(String title, String tag, boolean skip) { - Log.d(TAG, title + ": <" + tag + ">" + (skip ? " skip" : "")); - } - - private static void debugEndTag(String title, String tag, boolean skip) { - Log.d(TAG, title + ": " + (skip ? " skip" : "")); - } - - private static String debugInteger(TypedArray a, int index, String name) { - return a.hasValue(index) ? " " + name + "=" + a.getInt(index, 0) : ""; - } - - private static String debugBoolean(TypedArray a, int index, String name) { - return a.hasValue(index) ? " " + name + "=" + a.getBoolean(index, false) : ""; - } -} diff --git a/java/src/com/android/inputmethod/latin/BaseKeyboardView.java b/java/src/com/android/inputmethod/latin/BaseKeyboardView.java deleted file mode 100644 index 3193cd46e..000000000 --- a/java/src/com/android/inputmethod/latin/BaseKeyboardView.java +++ /dev/null @@ -1,1585 +0,0 @@ -/* - * Copyright (C) 2010 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.latin; - -import com.android.inputmethod.latin.BaseKeyboard.Key; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.Region.Op; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.util.Log; -import android.util.TypedValue; -import android.view.GestureDetector; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup.LayoutParams; -import android.view.WindowManager; -import android.widget.PopupWindow; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.WeakHashMap; - -/** - * A view that renders a virtual {@link LatinKeyboard}. It handles rendering of keys and - * detecting key presses and touch movements. - * - * TODO: References to LatinKeyboard in this class should be replaced with ones to its base class. - * - * @attr ref R.styleable#BaseKeyboardView_keyBackground - * @attr ref R.styleable#BaseKeyboardView_keyPreviewLayout - * @attr ref R.styleable#BaseKeyboardView_keyPreviewOffset - * @attr ref R.styleable#BaseKeyboardView_labelTextSize - * @attr ref R.styleable#BaseKeyboardView_keyTextSize - * @attr ref R.styleable#BaseKeyboardView_keyTextColor - * @attr ref R.styleable#BaseKeyboardView_verticalCorrection - * @attr ref R.styleable#BaseKeyboardView_popupLayout - */ -public class BaseKeyboardView extends View implements PointerTracker.UIProxy { - private static final String TAG = "BaseKeyboardView"; - private static final boolean DEBUG = false; - private static final boolean DEBUG_SHOW_ALIGN = false; - private static final boolean DEBUG_KEYBOARD_GRID = false; - - public static final int COLOR_SCHEME_WHITE = 0; - public static final int COLOR_SCHEME_BLACK = 1; - - public static final int NOT_A_TOUCH_COORDINATE = -1; - - public interface OnKeyboardActionListener { - - /** - * Called when the user presses a key. This is sent before the - * {@link #onKey} is called. For keys that repeat, this is only - * called once. - * - * @param primaryCode - * the unicode of the key being pressed. If the touch is - * not on a valid key, the value will be zero. - */ - void onPress(int primaryCode); - - /** - * Called when the user releases a key. This is sent after the - * {@link #onKey} is called. For keys that repeat, this is only - * called once. - * - * @param primaryCode - * the code of the key that was released - */ - void onRelease(int primaryCode); - - /** - * Send a key press to the listener. - * - * @param primaryCode - * this is the key that was pressed - * @param keyCodes - * the codes for all the possible alternative keys with - * the primary code being the first. If the primary key - * code is a single character such as an alphabet or - * number or symbol, the alternatives will include other - * characters that may be on the same key or adjacent - * keys. These codes are useful to correct for - * accidental presses of a key adjacent to the intended - * key. - * @param x - * x-coordinate pixel of touched event. If onKey is not called by onTouchEvent, - * the value should be NOT_A_TOUCH_COORDINATE. - * @param y - * y-coordinate pixel of touched event. If onKey is not called by onTouchEvent, - * the value should be NOT_A_TOUCH_COORDINATE. - */ - void onKey(int primaryCode, int[] keyCodes, int x, int y); - - /** - * Sends a sequence of characters to the listener. - * - * @param text - * the sequence of characters to be displayed. - */ - void onText(CharSequence text); - - /** - * Called when user released a finger outside any key. - */ - void onCancel(); - - /** - * Called when the user quickly moves the finger from right to - * left. - */ - void swipeLeft(); - - /** - * Called when the user quickly moves the finger from left to - * right. - */ - void swipeRight(); - - /** - * Called when the user quickly moves the finger from up to down. - */ - void swipeDown(); - - /** - * Called when the user quickly moves the finger from down to up. - */ - void swipeUp(); - } - - // Timing constants - private final int mKeyRepeatInterval; - - // Miscellaneous constants - /* package */ static final int NOT_A_KEY = -1; - private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable }; - private static final int HINT_ICON_VERTICAL_ADJUSTMENT_PIXEL = -1; - - // XML attribute - private int mKeyLetterSize; - private int mKeyTextColor; - private int mKeyTextColorDisabled; - private Typeface mKeyLetterStyle = Typeface.DEFAULT; - private int mLabelTextSize; - private int mColorScheme = COLOR_SCHEME_WHITE; - private int mShadowColor; - private float mShadowRadius; - private Drawable mKeyBackground; - private float mBackgroundDimAmount; - private float mKeyHysteresisDistance; - private float mVerticalCorrection; - private int mPreviewOffset; - private int mPreviewHeight; - private int mPopupLayout; - - // Main keyboard - private BaseKeyboard mKeyboard; - private Key[] mKeys; - - // Key preview popup - private boolean mInForeground; - private TextView mPreviewText; - private PopupWindow mPreviewPopup; - private int mPreviewTextSizeLarge; - private int[] mOffsetInWindow; - private int mOldPreviewKeyIndex = NOT_A_KEY; - private boolean mShowPreview = true; - private boolean mShowTouchPoints = true; - private int mPopupPreviewOffsetX; - private int mPopupPreviewOffsetY; - private int mWindowY; - private int mPopupPreviewDisplayedY; - private final int mDelayBeforePreview; - private final int mDelayAfterPreview; - - // Popup mini keyboard - private PopupWindow mMiniKeyboardPopup; - private BaseKeyboardView mMiniKeyboard; - private View mMiniKeyboardParent; - private final WeakHashMap mMiniKeyboardCache = new WeakHashMap(); - private int mMiniKeyboardOriginX; - private int mMiniKeyboardOriginY; - private long mMiniKeyboardPopupTime; - private int[] mWindowOffset; - private final float mMiniKeyboardSlideAllowance; - private int mMiniKeyboardTrackerId; - - /** Listener for {@link OnKeyboardActionListener}. */ - private OnKeyboardActionListener mKeyboardActionListener; - - private final ArrayList mPointerTrackers = new ArrayList(); - - // TODO: Let the PointerTracker class manage this pointer queue - private final PointerQueue mPointerQueue = new PointerQueue(); - - private final boolean mHasDistinctMultitouch; - private int mOldPointerCount = 1; - - protected KeyDetector mKeyDetector = new ProximityKeyDetector(); - - // Swipe gesture detector - private GestureDetector mGestureDetector; - private final SwipeTracker mSwipeTracker = new SwipeTracker(); - private final int mSwipeThreshold; - private final boolean mDisambiguateSwipe; - - // Drawing - /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/ - private boolean mDrawPending; - /** The dirty region in the keyboard bitmap */ - private final Rect mDirtyRect = new Rect(); - /** The keyboard bitmap for faster updates */ - private Bitmap mBuffer; - /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */ - private boolean mKeyboardChanged; - private Key mInvalidatedKey; - /** The canvas for the above mutable keyboard bitmap */ - private Canvas mCanvas; - private final Paint mPaint; - private final Rect mPadding; - private final Rect mClipRegion = new Rect(0, 0, 0, 0); - // This map caches key label text height in pixel as value and key label text size as map key. - private final HashMap mTextHeightCache = new HashMap(); - // Distance from horizontal center of the key, proportional to key label text height and width. - private final float KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR_CENTER = 0.55f; - private final float KEY_LABEL_VERTICAL_PADDING_FACTOR = 1.60f; - private final String KEY_LABEL_REFERENCE_CHAR = "H"; - private final int KEY_LABEL_OPTION_ALIGN_LEFT = 1; - private final int KEY_LABEL_OPTION_ALIGN_RIGHT = 2; - private final int KEY_LABEL_OPTION_ALIGN_BOTTOM = 8; - private final int KEY_LABEL_OPTION_FONT_NORMAL = 16; - private final int mKeyLabelHorizontalPadding; - - private final UIHandler mHandler = new UIHandler(); - - class UIHandler extends Handler { - private static final int MSG_POPUP_PREVIEW = 1; - private static final int MSG_DISMISS_PREVIEW = 2; - private static final int MSG_REPEAT_KEY = 3; - private static final int MSG_LONGPRESS_KEY = 4; - private static final int MSG_LONGPRESS_SHIFT_KEY = 5; - - private boolean mInKeyRepeat; - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_POPUP_PREVIEW: - showKey(msg.arg1, (PointerTracker)msg.obj); - break; - case MSG_DISMISS_PREVIEW: - mPreviewPopup.dismiss(); - break; - case MSG_REPEAT_KEY: { - final PointerTracker tracker = (PointerTracker)msg.obj; - tracker.repeatKey(msg.arg1); - startKeyRepeatTimer(mKeyRepeatInterval, msg.arg1, tracker); - break; - } - case MSG_LONGPRESS_KEY: { - final PointerTracker tracker = (PointerTracker)msg.obj; - openPopupIfRequired(msg.arg1, tracker); - break; - } - case MSG_LONGPRESS_SHIFT_KEY: { - final PointerTracker tracker = (PointerTracker)msg.obj; - onLongPressShiftKey(tracker); - break; - } - } - } - - public void popupPreview(long delay, int keyIndex, PointerTracker tracker) { - removeMessages(MSG_POPUP_PREVIEW); - if (mPreviewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) { - // Show right away, if it's already visible and finger is moving around - showKey(keyIndex, tracker); - } else { - sendMessageDelayed(obtainMessage(MSG_POPUP_PREVIEW, keyIndex, 0, tracker), - delay); - } - } - - public void cancelPopupPreview() { - removeMessages(MSG_POPUP_PREVIEW); - } - - public void dismissPreview(long delay) { - if (mPreviewPopup.isShowing()) { - sendMessageDelayed(obtainMessage(MSG_DISMISS_PREVIEW), delay); - } - } - - public void cancelDismissPreview() { - removeMessages(MSG_DISMISS_PREVIEW); - } - - public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) { - mInKeyRepeat = true; - sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay); - } - - public void cancelKeyRepeatTimer() { - mInKeyRepeat = false; - removeMessages(MSG_REPEAT_KEY); - } - - public boolean isInKeyRepeat() { - return mInKeyRepeat; - } - - public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) { - cancelLongPressTimers(); - sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay); - } - - public void startLongPressShiftTimer(long delay, int keyIndex, PointerTracker tracker) { - cancelLongPressTimers(); - sendMessageDelayed( - obtainMessage(MSG_LONGPRESS_SHIFT_KEY, keyIndex, 0, tracker), delay); - } - - public void cancelLongPressTimers() { - removeMessages(MSG_LONGPRESS_KEY); - removeMessages(MSG_LONGPRESS_SHIFT_KEY); - } - - public void cancelKeyTimers() { - cancelKeyRepeatTimer(); - cancelLongPressTimers(); - } - - public void cancelAllMessages() { - cancelKeyTimers(); - cancelPopupPreview(); - cancelDismissPreview(); - } - }; - - static class PointerQueue { - private LinkedList mQueue = new LinkedList(); - - public void add(PointerTracker tracker) { - mQueue.add(tracker); - } - - public int lastIndexOf(PointerTracker tracker) { - LinkedList queue = mQueue; - for (int index = queue.size() - 1; index >= 0; index--) { - PointerTracker t = queue.get(index); - if (t == tracker) - return index; - } - return -1; - } - - public void releaseAllPointersOlderThan(PointerTracker tracker, long eventTime) { - LinkedList queue = mQueue; - int oldestPos = 0; - for (PointerTracker t = queue.get(oldestPos); t != tracker; t = queue.get(oldestPos)) { - if (t.isModifier()) { - oldestPos++; - } else { - t.onUpEvent(t.getLastX(), t.getLastY(), eventTime); - t.setAlreadyProcessed(); - queue.remove(oldestPos); - } - } - } - - public void releaseAllPointersExcept(PointerTracker tracker, long eventTime) { - for (PointerTracker t : mQueue) { - if (t == tracker) - continue; - t.onUpEvent(t.getLastX(), t.getLastY(), eventTime); - t.setAlreadyProcessed(); - } - mQueue.clear(); - if (tracker != null) - mQueue.add(tracker); - } - - public void remove(PointerTracker tracker) { - mQueue.remove(tracker); - } - } - - public BaseKeyboardView(Context context, AttributeSet attrs) { - this(context, attrs, R.attr.keyboardViewStyle); - } - - public BaseKeyboardView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.BaseKeyboardView, defStyle, R.style.BaseKeyboardView); - LayoutInflater inflate = - (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - int previewLayout = 0; - int keyTextSize = 0; - - int n = a.getIndexCount(); - - for (int i = 0; i < n; i++) { - int attr = a.getIndex(i); - - switch (attr) { - case R.styleable.BaseKeyboardView_keyBackground: - mKeyBackground = a.getDrawable(attr); - break; - case R.styleable.BaseKeyboardView_keyHysteresisDistance: - mKeyHysteresisDistance = a.getDimensionPixelOffset(attr, 0); - break; - case R.styleable.BaseKeyboardView_verticalCorrection: - mVerticalCorrection = a.getDimensionPixelOffset(attr, 0); - break; - case R.styleable.BaseKeyboardView_keyPreviewLayout: - previewLayout = a.getResourceId(attr, 0); - break; - case R.styleable.BaseKeyboardView_keyPreviewOffset: - mPreviewOffset = a.getDimensionPixelOffset(attr, 0); - break; - case R.styleable.BaseKeyboardView_keyPreviewHeight: - mPreviewHeight = a.getDimensionPixelSize(attr, 80); - break; - case R.styleable.BaseKeyboardView_keyLetterSize: - mKeyLetterSize = a.getDimensionPixelSize(attr, 18); - break; - case R.styleable.BaseKeyboardView_keyTextColor: - mKeyTextColor = a.getColor(attr, 0xFF000000); - break; - case R.styleable.BaseKeyboardView_keyTextColorDisabled: - mKeyTextColorDisabled = a.getColor(attr, 0xFF000000); - break; - case R.styleable.BaseKeyboardView_labelTextSize: - mLabelTextSize = a.getDimensionPixelSize(attr, 14); - break; - case R.styleable.BaseKeyboardView_popupLayout: - mPopupLayout = a.getResourceId(attr, 0); - break; - case R.styleable.BaseKeyboardView_shadowColor: - mShadowColor = a.getColor(attr, 0); - break; - case R.styleable.BaseKeyboardView_shadowRadius: - mShadowRadius = a.getFloat(attr, 0f); - break; - // TODO: Use Theme (android.R.styleable.Theme_backgroundDimAmount) - case R.styleable.BaseKeyboardView_backgroundDimAmount: - mBackgroundDimAmount = a.getFloat(attr, 0.5f); - break; - case R.styleable.BaseKeyboardView_keyLetterStyle: - mKeyLetterStyle = Typeface.defaultFromStyle(a.getInt(attr, Typeface.NORMAL)); - break; - case R.styleable.BaseKeyboardView_colorScheme: - mColorScheme = a.getInt(attr, COLOR_SCHEME_WHITE); - break; - } - } - - final Resources res = getResources(); - - mPreviewPopup = new PopupWindow(context); - if (previewLayout != 0) { - mPreviewText = (TextView) inflate.inflate(previewLayout, null); - mPreviewTextSizeLarge = (int) res.getDimension(R.dimen.key_preview_text_size_large); - mPreviewPopup.setContentView(mPreviewText); - mPreviewPopup.setBackgroundDrawable(null); - } else { - mShowPreview = false; - } - mPreviewPopup.setTouchable(false); - mPreviewPopup.setAnimationStyle(R.style.KeyPreviewAnimation); - mDelayBeforePreview = res.getInteger(R.integer.config_delay_before_preview); - mDelayAfterPreview = res.getInteger(R.integer.config_delay_after_preview); - mKeyLabelHorizontalPadding = (int)res.getDimension( - R.dimen.key_label_horizontal_alignment_padding); - - mMiniKeyboardParent = this; - mMiniKeyboardPopup = new PopupWindow(context); - mMiniKeyboardPopup.setBackgroundDrawable(null); - mMiniKeyboardPopup.setAnimationStyle(R.style.MiniKeyboardAnimation); - - mPaint = new Paint(); - mPaint.setAntiAlias(true); - mPaint.setTextSize(keyTextSize); - mPaint.setTextAlign(Align.CENTER); - mPaint.setAlpha(255); - - mPadding = new Rect(0, 0, 0, 0); - mKeyBackground.getPadding(mPadding); - - mSwipeThreshold = (int) (500 * res.getDisplayMetrics().density); - // TODO: Refer frameworks/base/core/res/res/values/config.xml - mDisambiguateSwipe = res.getBoolean(R.bool.config_swipeDisambiguation); - mMiniKeyboardSlideAllowance = res.getDimension(R.dimen.mini_keyboard_slide_allowance); - - GestureDetector.SimpleOnGestureListener listener = - new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX, - float velocityY) { - final float absX = Math.abs(velocityX); - final float absY = Math.abs(velocityY); - float deltaX = me2.getX() - me1.getX(); - float deltaY = me2.getY() - me1.getY(); - int travelX = getWidth() / 2; // Half the keyboard width - int travelY = getHeight() / 2; // Half the keyboard height - mSwipeTracker.computeCurrentVelocity(1000); - final float endingVelocityX = mSwipeTracker.getXVelocity(); - final float endingVelocityY = mSwipeTracker.getYVelocity(); - if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) { - if (mDisambiguateSwipe && endingVelocityX >= velocityX / 4) { - swipeRight(); - return true; - } - } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) { - if (mDisambiguateSwipe && endingVelocityX <= velocityX / 4) { - swipeLeft(); - return true; - } - } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) { - if (mDisambiguateSwipe && endingVelocityY <= velocityY / 4) { - swipeUp(); - return true; - } - } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) { - if (mDisambiguateSwipe && endingVelocityY >= velocityY / 4) { - swipeDown(); - return true; - } - } - return false; - } - }; - - final boolean ignoreMultitouch = true; - mGestureDetector = new GestureDetector(getContext(), listener, null, ignoreMultitouch); - mGestureDetector.setIsLongpressEnabled(false); - - mHasDistinctMultitouch = context.getPackageManager() - .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); - mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval); - } - - public void setOnKeyboardActionListener(OnKeyboardActionListener listener) { - mKeyboardActionListener = listener; - for (PointerTracker tracker : mPointerTrackers) { - tracker.setOnKeyboardActionListener(listener); - } - } - - /** - * Returns the {@link OnKeyboardActionListener} object. - * @return the listener attached to this keyboard - */ - protected OnKeyboardActionListener getOnKeyboardActionListener() { - return mKeyboardActionListener; - } - - /** - * Attaches a keyboard to this view. The keyboard can be switched at any time and the - * view will re-layout itself to accommodate the keyboard. - * @see BaseKeyboard - * @see #getKeyboard() - * @param keyboard the keyboard to display in this view - */ - protected void setKeyboard(BaseKeyboard keyboard) { - if (mKeyboard != null) { - dismissKeyPreview(); - } - // Remove any pending messages, except dismissing preview - mHandler.cancelKeyTimers(); - mHandler.cancelPopupPreview(); - mKeyboard = keyboard; - LatinImeLogger.onSetKeyboard(keyboard); - mKeys = mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), - -getPaddingTop() + mVerticalCorrection); - for (PointerTracker tracker : mPointerTrackers) { - tracker.setKeyboard(keyboard, mKeys, mKeyHysteresisDistance); - } - requestLayout(); - // Hint to reallocate the buffer if the size changed - mKeyboardChanged = true; - invalidateAllKeys(); - computeProximityThreshold(keyboard, mKeys); - mMiniKeyboardCache.clear(); - } - - /** - * Returns the current keyboard being displayed by this view. - * @return the currently attached keyboard - * @see #setKeyboard(BaseKeyboard) - */ - protected BaseKeyboard getKeyboard() { - return mKeyboard; - } - - /** - * Return whether the device has distinct multi-touch panel. - * @return true if the device has distinct multi-touch panel. - */ - public boolean hasDistinctMultitouch() { - return mHasDistinctMultitouch; - } - - /** - * Enables or disables the key feedback popup. This is a popup that shows a magnified - * version of the depressed key. By default the preview is enabled. - * @param previewEnabled whether or not to enable the key feedback popup - * @see #isPreviewEnabled() - */ - public void setPreviewEnabled(boolean previewEnabled) { - mShowPreview = previewEnabled; - } - - /** - * Returns the enabled state of the key feedback popup. - * @return whether or not the key feedback popup is enabled - * @see #setPreviewEnabled(boolean) - */ - public boolean isPreviewEnabled() { - return mShowPreview; - } - - public int getColorScheme() { - return mColorScheme; - } - - public void setPopupParent(View v) { - mMiniKeyboardParent = v; - } - - public void setPopupOffset(int x, int y) { - mPopupPreviewOffsetX = x; - mPopupPreviewOffsetY = y; - mPreviewPopup.dismiss(); - } - - /** - * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key - * codes for adjacent keys. When disabled, only the primary key code will be - * reported. - * @param enabled whether or not the proximity correction is enabled - */ - public void setProximityCorrectionEnabled(boolean enabled) { - mKeyDetector.setProximityCorrectionEnabled(enabled); - } - - /** - * Returns true if proximity correction is enabled. - */ - public boolean isProximityCorrectionEnabled() { - return mKeyDetector.isProximityCorrectionEnabled(); - } - - protected CharSequence adjustCase(CharSequence label) { - if (mKeyboard.isShiftedOrShiftLocked() && label != null && label.length() < 3 - && Character.isLowerCase(label.charAt(0))) { - label = label.toString().toUpperCase(); - } - return label; - } - - @Override - public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // Round up a little - if (mKeyboard == null) { - setMeasuredDimension( - getPaddingLeft() + getPaddingRight(), getPaddingTop() + getPaddingBottom()); - } else { - int width = mKeyboard.getMinWidth() + getPaddingLeft() + getPaddingRight(); - if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) { - width = MeasureSpec.getSize(widthMeasureSpec); - } - setMeasuredDimension( - width, mKeyboard.getHeight() + getPaddingTop() + getPaddingBottom()); - } - } - - /** - * Compute the most common key width and use it as proximity key detection threshold. - * @param keyboard - * @param keys - */ - private void computeProximityThreshold(BaseKeyboard keyboard, Key[] keys) { - if (keyboard == null || keys == null || keys.length == 0) return; - final HashMap histogram = new HashMap(); - int maxCount = 0; - int mostCommonWidth = 0; - for (Key key : keys) { - final Integer width = key.width + key.gap; - Integer count = histogram.get(width); - if (count == null) - count = 0; - histogram.put(width, ++count); - if (count > maxCount) { - maxCount = count; - mostCommonWidth = width; - } - } - mKeyDetector.setProximityThreshold(mostCommonWidth); - } - - @Override - public void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - // Release the buffer, if any and it will be reallocated on the next draw - mBuffer = null; - } - - @Override - public void onDraw(Canvas canvas) { - super.onDraw(canvas); - if (mDrawPending || mBuffer == null || mKeyboardChanged) { - onBufferDraw(); - } - canvas.drawBitmap(mBuffer, 0, 0, null); - } - - private void onBufferDraw() { - if (mBuffer == null || mKeyboardChanged) { - if (mBuffer == null || mKeyboardChanged && - (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) { - // Make sure our bitmap is at least 1x1 - final int width = Math.max(1, getWidth()); - final int height = Math.max(1, getHeight()); - mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - mCanvas = new Canvas(mBuffer); - } - invalidateAllKeys(); - mKeyboardChanged = false; - } - final Canvas canvas = mCanvas; - canvas.clipRect(mDirtyRect, Op.REPLACE); - - if (mKeyboard == null) return; - - final Paint paint = mPaint; - final Drawable keyBackground = mKeyBackground; - final Rect clipRegion = mClipRegion; - final Rect padding = mPadding; - final int kbdPaddingLeft = getPaddingLeft(); - final int kbdPaddingTop = getPaddingTop(); - final Key[] keys = mKeys; - final Key invalidKey = mInvalidatedKey; - final boolean isManualTemporaryUpperCase = (mKeyboard instanceof LatinKeyboard - && ((LatinKeyboard)mKeyboard).isManualTemporaryUpperCase()); - - boolean drawSingleKey = false; - if (invalidKey != null && canvas.getClipBounds(clipRegion)) { - // TODO we should use Rect.inset and Rect.contains here. - // Is clipRegion completely contained within the invalidated key? - if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left && - invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top && - invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right && - invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) { - drawSingleKey = true; - } - } - canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR); - final int keyCount = keys.length; - for (int i = 0; i < keyCount; i++) { - final Key key = keys[i]; - if (drawSingleKey && invalidKey != key) { - continue; - } - int[] drawableState = key.getCurrentDrawableState(); - keyBackground.setState(drawableState); - - // Switch the character to uppercase if shift is pressed - String label = key.label == null? null : adjustCase(key.label).toString(); - - final Rect bounds = keyBackground.getBounds(); - if (key.width != bounds.right || key.height != bounds.bottom) { - keyBackground.setBounds(0, 0, key.width, key.height); - } - canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop); - keyBackground.draw(canvas); - - final int rowHeight = padding.top + key.height; - // Draw key label - if (label != null) { - // For characters, use large font. For labels like "Done", use small font. - final int labelSize = getLabelSizeAndSetPaint(label, key, paint); - final int labelCharHeight = getLabelCharHeight(labelSize, paint); - - // Vertical label text alignment. - final float baseline; - if ((key.labelOption & KEY_LABEL_OPTION_ALIGN_BOTTOM) != 0) { - baseline = key.height - - + labelCharHeight * KEY_LABEL_VERTICAL_PADDING_FACTOR; - if (DEBUG_SHOW_ALIGN) - drawHorizontalLine(canvas, (int)baseline, key.width, 0xc0008000, - new Paint()); - } else { // Align center - final float centerY = (key.height + padding.top - padding.bottom) / 2; - baseline = centerY - + labelCharHeight * KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR_CENTER; - } - // Horizontal label text alignment - final int positionX; - if ((key.labelOption & KEY_LABEL_OPTION_ALIGN_LEFT) != 0) { - positionX = mKeyLabelHorizontalPadding + padding.left; - paint.setTextAlign(Align.LEFT); - if (DEBUG_SHOW_ALIGN) - drawVerticalLine(canvas, positionX, rowHeight, 0xc0800080, new Paint()); - } else if ((key.labelOption & KEY_LABEL_OPTION_ALIGN_RIGHT) != 0) { - positionX = key.width - mKeyLabelHorizontalPadding - padding.right; - paint.setTextAlign(Align.RIGHT); - if (DEBUG_SHOW_ALIGN) - drawVerticalLine(canvas, positionX, rowHeight, 0xc0808000, new Paint()); - } else { - positionX = (key.width + padding.left - padding.right) / 2; - paint.setTextAlign(Align.CENTER); - if (DEBUG_SHOW_ALIGN && label.length() > 1) - drawVerticalLine(canvas, positionX, rowHeight, 0xc0008080, new Paint()); - } - if (key.manualTemporaryUpperCaseHintIcon != null && isManualTemporaryUpperCase) { - paint.setColor(mKeyTextColorDisabled); - } else { - paint.setColor(mKeyTextColor); - } - // Set a drop shadow for the text - paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor); - canvas.drawText(label, positionX, baseline, paint); - // Turn off drop shadow - paint.setShadowLayer(0, 0, 0, 0); - } - // Draw key icon - if (key.label == null && key.icon != null) { - final int drawableWidth = key.icon.getIntrinsicWidth(); - final int drawableHeight = key.icon.getIntrinsicHeight(); - final int drawableX; - final int drawableY = ( - key.height + padding.top - padding.bottom - drawableHeight) / 2; - if ((key.labelOption & KEY_LABEL_OPTION_ALIGN_LEFT) != 0) { - drawableX = padding.left + mKeyLabelHorizontalPadding; - if (DEBUG_SHOW_ALIGN) - drawVerticalLine(canvas, drawableX, rowHeight, 0xc0800080, new Paint()); - } else if ((key.labelOption & KEY_LABEL_OPTION_ALIGN_RIGHT) != 0) { - drawableX = key.width - padding.right - mKeyLabelHorizontalPadding - - drawableWidth; - if (DEBUG_SHOW_ALIGN) - drawVerticalLine(canvas, drawableX + drawableWidth, rowHeight, - 0xc0808000, new Paint()); - } else { // Align center - drawableX = (key.width + padding.left - padding.right - drawableWidth) / 2; - if (DEBUG_SHOW_ALIGN) - drawVerticalLine(canvas, drawableX + drawableWidth / 2, rowHeight, - 0xc0008080, new Paint()); - } - drawIcon(canvas, key.icon, drawableX, drawableY, drawableWidth, drawableHeight); - if (DEBUG_SHOW_ALIGN) - drawRectangle(canvas, drawableX, drawableY, drawableWidth, drawableHeight, - 0x80c00000, new Paint()); - } - if (key.hintIcon != null) { - final int drawableWidth = key.width; - final int drawableHeight = key.height; - final int drawableX = 0; - final int drawableY = HINT_ICON_VERTICAL_ADJUSTMENT_PIXEL; - Drawable icon = (isManualTemporaryUpperCase - && key.manualTemporaryUpperCaseHintIcon != null) - ? key.manualTemporaryUpperCaseHintIcon : key.hintIcon; - drawIcon(canvas, icon, drawableX, drawableY, drawableWidth, drawableHeight); - if (DEBUG_SHOW_ALIGN) - drawRectangle(canvas, drawableX, drawableY, drawableWidth, drawableHeight, - 0x80c0c000, new Paint()); - } - canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop); - } - - if (DEBUG_KEYBOARD_GRID) { - Paint p = new Paint(); - p.setStyle(Paint.Style.STROKE); - p.setStrokeWidth(1.0f); - p.setColor(0x800000c0); - int cw = (mKeyboard.getMinWidth() + mKeyboard.GRID_WIDTH - 1) / mKeyboard.GRID_WIDTH; - int ch = (mKeyboard.getHeight() + mKeyboard.GRID_HEIGHT - 1) / mKeyboard.GRID_HEIGHT; - for (int i = 0; i <= mKeyboard.GRID_WIDTH; i++) - canvas.drawLine(i * cw, 0, i * cw, ch * mKeyboard.GRID_HEIGHT, p); - for (int i = 0; i <= mKeyboard.GRID_HEIGHT; i++) - canvas.drawLine(0, i * ch, cw * mKeyboard.GRID_WIDTH, i * ch, p); - } - - mInvalidatedKey = null; - // Overlay a dark rectangle to dim the keyboard - if (mMiniKeyboard != null) { - paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24); - canvas.drawRect(0, 0, getWidth(), getHeight(), paint); - } - - if (DEBUG) { - if (mShowTouchPoints) { - for (PointerTracker tracker : mPointerTrackers) { - int startX = tracker.getStartX(); - int startY = tracker.getStartY(); - int lastX = tracker.getLastX(); - int lastY = tracker.getLastY(); - paint.setAlpha(128); - paint.setColor(0xFFFF0000); - canvas.drawCircle(startX, startY, 3, paint); - canvas.drawLine(startX, startY, lastX, lastY, paint); - paint.setColor(0xFF0000FF); - canvas.drawCircle(lastX, lastY, 3, paint); - paint.setColor(0xFF00FF00); - canvas.drawCircle((startX + lastX) / 2, (startY + lastY) / 2, 2, paint); - } - } - } - - mDrawPending = false; - mDirtyRect.setEmpty(); - } - - private int getLabelSizeAndSetPaint(CharSequence label, Key key, Paint paint) { - // For characters, use large font. For labels like "Done", use small font. - final int labelSize; - final Typeface labelStyle; - if (label.length() > 1 && key.codes.length < 2) { - labelSize = mLabelTextSize; - if ((key.labelOption & KEY_LABEL_OPTION_FONT_NORMAL) != 0) { - labelStyle = Typeface.DEFAULT; - } else { - labelStyle = Typeface.DEFAULT_BOLD; - } - } else { - labelSize = mKeyLetterSize; - labelStyle = mKeyLetterStyle; - } - paint.setTextSize(labelSize); - paint.setTypeface(labelStyle); - return labelSize; - } - - private int getLabelCharHeight(int labelSize, Paint paint) { - Integer labelHeightValue = mTextHeightCache.get(labelSize); - final int labelCharHeight; - if (labelHeightValue != null) { - labelCharHeight = labelHeightValue; - } else { - Rect textBounds = new Rect(); - paint.getTextBounds(KEY_LABEL_REFERENCE_CHAR, 0, 1, textBounds); - labelCharHeight = textBounds.height(); - mTextHeightCache.put(labelSize, labelCharHeight); - } - return labelCharHeight; - } - - private static void drawIcon(Canvas canvas, Drawable icon, int x, int y, int width, - int height) { - canvas.translate(x, y); - icon.setBounds(0, 0, width, height); - icon.draw(canvas); - canvas.translate(-x, -y); - } - - private static void drawHorizontalLine(Canvas canvas, int y, int w, int color, Paint paint) { - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(1.0f); - paint.setColor(color); - canvas.drawLine(0, y, w, y, paint); - } - - private static void drawVerticalLine(Canvas canvas, int x, int h, int color, Paint paint) { - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(1.0f); - paint.setColor(color); - canvas.drawLine(x, 0, x, h, paint); - } - - private static void drawRectangle(Canvas canvas, int x, int y, int w, int h, int color, - Paint paint) { - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(1.0f); - paint.setColor(color); - canvas.translate(x, y); - canvas.drawRect(0, 0, w, h, paint); - canvas.translate(-x, -y); - } - - public void setForeground(boolean foreground) { - mInForeground = foreground; - } - - // TODO: clean up this method. - private void dismissKeyPreview() { - for (PointerTracker tracker : mPointerTrackers) - tracker.releaseKey(); - showPreview(NOT_A_KEY, null); - } - - public void showPreview(int keyIndex, PointerTracker tracker) { - int oldKeyIndex = mOldPreviewKeyIndex; - mOldPreviewKeyIndex = keyIndex; - // We should re-draw popup preview when 1) we need to hide the preview, 2) we will show - // the space key preview and 3) pointer moves off the space key to other letter key, we - // should hide the preview of the previous key. - final boolean hidePreviewOrShowSpaceKeyPreview = (tracker == null) - || (SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCHER - && SubtypeSwitcher.getInstance().needsToDisplayLanguage() - && (tracker.isSpaceKey(keyIndex) || tracker.isSpaceKey(oldKeyIndex))); - // If key changed and preview is on or the key is space (language switch is enabled) - if (oldKeyIndex != keyIndex && (mShowPreview || (hidePreviewOrShowSpaceKeyPreview))) { - if (keyIndex == NOT_A_KEY) { - mHandler.cancelPopupPreview(); - mHandler.dismissPreview(mDelayAfterPreview); - } else if (tracker != null) { - mHandler.popupPreview(mDelayBeforePreview, keyIndex, tracker); - } - } - } - - // TODO Must fix popup preview on xlarge layout - private void showKey(final int keyIndex, PointerTracker tracker) { - Key key = tracker.getKey(keyIndex); - // If keyIndex is invalid or IME is already closed, we must not show key preview. - // Trying to show preview PopupWindow while root window is closed causes - // WindowManager.BadTokenException. - if (key == null || !mInForeground) - return; - // What we show as preview should match what we show on key top in onBufferDraw(). - if (key.label != null) { - // TODO Should take care of temporaryShiftLabel here. - mPreviewText.setCompoundDrawables(null, null, null, null); - mPreviewText.setText(adjustCase(tracker.getPreviewText(key))); - if (key.label.length() > 1 && key.codes.length < 2) { - mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyLetterSize); - mPreviewText.setTypeface(Typeface.DEFAULT_BOLD); - } else { - mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge); - mPreviewText.setTypeface(mKeyLetterStyle); - } - } else { - mPreviewText.setCompoundDrawables(null, null, null, - key.iconPreview != null ? key.iconPreview : key.icon); - mPreviewText.setText(null); - } - mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width - + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight()); - final int popupHeight = mPreviewHeight; - LayoutParams lp = mPreviewText.getLayoutParams(); - if (lp != null) { - lp.width = popupWidth; - lp.height = popupHeight; - } - - int popupPreviewX = key.x - (popupWidth - key.width) / 2; - int popupPreviewY = key.y - popupHeight + mPreviewOffset; - - mHandler.cancelDismissPreview(); - if (mOffsetInWindow == null) { - mOffsetInWindow = new int[2]; - getLocationInWindow(mOffsetInWindow); - mOffsetInWindow[0] += mPopupPreviewOffsetX; // Offset may be zero - mOffsetInWindow[1] += mPopupPreviewOffsetY; // Offset may be zero - int[] windowLocation = new int[2]; - getLocationOnScreen(windowLocation); - mWindowY = windowLocation[1]; - } - // Set the preview background state - mPreviewText.getBackground().setState( - key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET); - popupPreviewX += mOffsetInWindow[0]; - popupPreviewY += mOffsetInWindow[1]; - - // If the popup cannot be shown above the key, put it on the side - if (popupPreviewY + mWindowY < 0) { - // If the key you're pressing is on the left side of the keyboard, show the popup on - // the right, offset by enough to see at least one key to the left/right. - if (key.x + key.width <= getWidth() / 2) { - popupPreviewX += (int) (key.width * 2.5); - } else { - popupPreviewX -= (int) (key.width * 2.5); - } - popupPreviewY += popupHeight; - } - - try { - if (mPreviewPopup.isShowing()) { - mPreviewPopup.update(popupPreviewX, popupPreviewY, popupWidth, popupHeight); - } else { - mPreviewPopup.setWidth(popupWidth); - mPreviewPopup.setHeight(popupHeight); - mPreviewPopup.showAtLocation(mMiniKeyboardParent, Gravity.NO_GRAVITY, - popupPreviewX, popupPreviewY); - } - } catch (WindowManager.BadTokenException e) { - // Swallow the exception which will be happened when IME is already closed. - Log.w(TAG, "LatinIME is already closed when tried showing key preview."); - } - // Record popup preview position to display mini-keyboard later at the same positon - mPopupPreviewDisplayedY = popupPreviewY; - mPreviewText.setVisibility(VISIBLE); - } - - /** - * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient - * because the keyboard renders the keys to an off-screen buffer and an invalidate() only - * draws the cached buffer. - * @see #invalidateKey(Key) - */ - public void invalidateAllKeys() { - mDirtyRect.union(0, 0, getWidth(), getHeight()); - mDrawPending = true; - invalidate(); - } - - /** - * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only - * one key is changing it's content. Any changes that affect the position or size of the key - * may not be honored. - * @param key key in the attached {@link BaseKeyboard}. - * @see #invalidateAllKeys - */ - public void invalidateKey(Key key) { - if (key == null) - return; - mInvalidatedKey = key; - // TODO we should clean up this and record key's region to use in onBufferDraw. - mDirtyRect.union(key.x + getPaddingLeft(), key.y + getPaddingTop(), - key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop()); - onBufferDraw(); - invalidate(key.x + getPaddingLeft(), key.y + getPaddingTop(), - key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop()); - } - - private boolean openPopupIfRequired(int keyIndex, PointerTracker tracker) { - // Check if we have a popup layout specified first. - if (mPopupLayout == 0) { - return false; - } - - Key popupKey = tracker.getKey(keyIndex); - if (popupKey == null) - return false; - boolean result = onLongPress(popupKey); - if (result) { - dismissKeyPreview(); - mMiniKeyboardTrackerId = tracker.mPointerId; - // Mark this tracker "already processed" and remove it from the pointer queue - tracker.setAlreadyProcessed(); - mPointerQueue.remove(tracker); - } - return result; - } - - private void onLongPressShiftKey(PointerTracker tracker) { - tracker.setAlreadyProcessed(); - mPointerQueue.remove(tracker); - mKeyboardActionListener.onKey(LatinKeyboardView.KEYCODE_CAPSLOCK, null, 0, 0); - } - - private View inflateMiniKeyboardContainer(Key popupKey) { - int popupKeyboardId = popupKey.popupResId; - LayoutInflater inflater = (LayoutInflater)getContext().getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - View container = inflater.inflate(mPopupLayout, null); - if (container == null) - throw new NullPointerException(); - - BaseKeyboardView miniKeyboard = - (BaseKeyboardView)container.findViewById(R.id.BaseKeyboardView); - miniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() { - public void onKey(int primaryCode, int[] keyCodes, int x, int y) { - mKeyboardActionListener.onKey(primaryCode, keyCodes, x, y); - dismissPopupKeyboard(); - } - - public void onText(CharSequence text) { - mKeyboardActionListener.onText(text); - dismissPopupKeyboard(); - } - - public void onCancel() { - dismissPopupKeyboard(); - } - - public void swipeLeft() { - } - public void swipeRight() { - } - public void swipeUp() { - } - public void swipeDown() { - } - public void onPress(int primaryCode) { - mKeyboardActionListener.onPress(primaryCode); - } - public void onRelease(int primaryCode) { - mKeyboardActionListener.onRelease(primaryCode); - } - }); - // Override default ProximityKeyDetector. - miniKeyboard.mKeyDetector = new MiniKeyboardKeyDetector(mMiniKeyboardSlideAllowance); - // Remove gesture detector on mini-keyboard - miniKeyboard.mGestureDetector = null; - - BaseKeyboard keyboard; - if (popupKey.popupCharacters != null) { - keyboard = new BaseKeyboard(getContext(), popupKeyboardId, popupKey.popupCharacters, - -1, getPaddingLeft() + getPaddingRight()); - } else { - keyboard = new BaseKeyboard(getContext(), popupKeyboardId); - } - miniKeyboard.setKeyboard(keyboard); - miniKeyboard.setPopupParent(this); - - container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), - MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST)); - - return container; - } - - private static boolean isOneRowKeys(List keys) { - if (keys.size() == 0) return false; - final int edgeFlags = keys.get(0).edgeFlags; - // HACK: The first key of mini keyboard which was inflated from xml and has multiple rows, - // does not have both top and bottom edge flags on at the same time. On the other hand, - // the first key of mini keyboard that was created with popupCharacters must have both top - // and bottom edge flags on. - // When you want to use one row mini-keyboard from xml file, make sure that the row has - // both top and bottom edge flags set. - return (edgeFlags & BaseKeyboard.EDGE_TOP) != 0 - && (edgeFlags & BaseKeyboard.EDGE_BOTTOM) != 0; - } - - /** - * Called when a key is long pressed. By default this will open any popup keyboard associated - * with this key through the attributes popupLayout and popupCharacters. - * @param popupKey the key that was long pressed - * @return true if the long press is handled, false otherwise. Subclasses should call the - * method on the base class if the subclass doesn't wish to handle the call. - */ - protected boolean onLongPress(Key popupKey) { - // TODO if popupKey.popupCharacters has only one letter, send it as key without opening - // mini keyboard. - - if (popupKey.popupResId == 0) - return false; - - View container = mMiniKeyboardCache.get(popupKey); - if (container == null) { - container = inflateMiniKeyboardContainer(popupKey); - mMiniKeyboardCache.put(popupKey, container); - } - mMiniKeyboard = (BaseKeyboardView)container.findViewById(R.id.BaseKeyboardView); - if (mWindowOffset == null) { - mWindowOffset = new int[2]; - getLocationInWindow(mWindowOffset); - } - - // Get width of a key in the mini popup keyboard = "miniKeyWidth". - // On the other hand, "popupKey.width" is width of the pressed key on the main keyboard. - // We adjust the position of mini popup keyboard with the edge key in it: - // a) When we have the leftmost key in popup keyboard directly above the pressed key - // Right edges of both keys should be aligned for consistent default selection - // b) When we have the rightmost key in popup keyboard directly above the pressed key - // Left edges of both keys should be aligned for consistent default selection - final List miniKeys = mMiniKeyboard.getKeyboard().getKeys(); - final int miniKeyWidth = miniKeys.size() > 0 ? miniKeys.get(0).width : 0; - - // HACK: Have the leftmost number in the popup characters right above the key - boolean isNumberAtLeftmost = - hasMultiplePopupChars(popupKey) && isNumberAtLeftmostPopupChar(popupKey); - int popupX = popupKey.x + mWindowOffset[0]; - popupX += getPaddingLeft(); - if (isNumberAtLeftmost) { - popupX += popupKey.width - miniKeyWidth; // adjustment for a) described above - popupX -= container.getPaddingLeft(); - } else { - popupX += miniKeyWidth; // adjustment for b) described above - popupX -= container.getMeasuredWidth(); - popupX += container.getPaddingRight(); - } - int popupY = popupKey.y + mWindowOffset[1]; - popupY += getPaddingTop(); - popupY -= container.getMeasuredHeight(); - popupY += container.getPaddingBottom(); - final int x = popupX; - final int y = mShowPreview && isOneRowKeys(miniKeys) ? mPopupPreviewDisplayedY : popupY; - - int adjustedX = x; - if (x < 0) { - adjustedX = 0; - } else if (x > (getMeasuredWidth() - container.getMeasuredWidth())) { - adjustedX = getMeasuredWidth() - container.getMeasuredWidth(); - } - mMiniKeyboardOriginX = adjustedX + container.getPaddingLeft() - mWindowOffset[0]; - mMiniKeyboardOriginY = y + container.getPaddingTop() - mWindowOffset[1]; - mMiniKeyboard.setPopupOffset(adjustedX, y); - // TODO: change the below line to use getLatinKeyboard() instead of getKeyboard() - BaseKeyboard baseMiniKeyboard = mMiniKeyboard.getKeyboard(); - if (baseMiniKeyboard != null && baseMiniKeyboard.setShifted(mKeyboard == null - ? false : mKeyboard.isShiftedOrShiftLocked())) { - mMiniKeyboard.invalidateAllKeys(); - } - // Mini keyboard needs no pop-up key preview displayed. - mMiniKeyboard.setPreviewEnabled(false); - mMiniKeyboardPopup.setContentView(container); - mMiniKeyboardPopup.setWidth(container.getMeasuredWidth()); - mMiniKeyboardPopup.setHeight(container.getMeasuredHeight()); - mMiniKeyboardPopup.showAtLocation(this, Gravity.NO_GRAVITY, x, y); - - // Inject down event on the key to mini keyboard. - long eventTime = SystemClock.uptimeMillis(); - mMiniKeyboardPopupTime = eventTime; - MotionEvent downEvent = generateMiniKeyboardMotionEvent(MotionEvent.ACTION_DOWN, popupKey.x - + popupKey.width / 2, popupKey.y + popupKey.height / 2, eventTime); - mMiniKeyboard.onTouchEvent(downEvent); - downEvent.recycle(); - - invalidateAllKeys(); - return true; - } - - private static boolean hasMultiplePopupChars(Key key) { - if (key.popupCharacters != null && key.popupCharacters.length() > 1) { - return true; - } - return false; - } - - private static boolean isNumberAtLeftmostPopupChar(Key key) { - if (key.popupCharacters != null && key.popupCharacters.length() > 0 - && isAsciiDigit(key.popupCharacters.charAt(0))) { - return true; - } - return false; - } - - private static boolean isAsciiDigit(char c) { - return (c < 0x80) && Character.isDigit(c); - } - - private MotionEvent generateMiniKeyboardMotionEvent(int action, int x, int y, long eventTime) { - return MotionEvent.obtain(mMiniKeyboardPopupTime, eventTime, action, - x - mMiniKeyboardOriginX, y - mMiniKeyboardOriginY, 0); - } - - private PointerTracker getPointerTracker(final int id) { - final ArrayList pointers = mPointerTrackers; - final Key[] keys = mKeys; - final OnKeyboardActionListener listener = mKeyboardActionListener; - - // Create pointer trackers until we can get 'id+1'-th tracker, if needed. - for (int i = pointers.size(); i <= id; i++) { - final PointerTracker tracker = - new PointerTracker(i, mHandler, mKeyDetector, this, getResources()); - if (keys != null) - tracker.setKeyboard(mKeyboard, keys, mKeyHysteresisDistance); - if (listener != null) - tracker.setOnKeyboardActionListener(listener); - pointers.add(tracker); - } - - return pointers.get(id); - } - - @Override - public boolean onTouchEvent(MotionEvent me) { - final int pointerCount = me.getPointerCount(); - final int action = me.getActionMasked(); - - // TODO: cleanup this code into a multi-touch to single-touch event converter class? - // If the device does not have distinct multi-touch support panel, ignore all multi-touch - // events except a transition from/to single-touch. - if (!mHasDistinctMultitouch && pointerCount > 1 && mOldPointerCount > 1) { - return true; - } - - // Track the last few movements to look for spurious swipes. - mSwipeTracker.addMovement(me); - - // Gesture detector must be enabled only when mini-keyboard is not on the screen. - if (mMiniKeyboard == null - && mGestureDetector != null && mGestureDetector.onTouchEvent(me)) { - dismissKeyPreview(); - mHandler.cancelKeyTimers(); - return true; - } - - final long eventTime = me.getEventTime(); - final int index = me.getActionIndex(); - final int id = me.getPointerId(index); - final int x = (int)me.getX(index); - final int y = (int)me.getY(index); - - // Needs to be called after the gesture detector gets a turn, as it may have - // displayed the mini keyboard - if (mMiniKeyboard != null) { - final int miniKeyboardPointerIndex = me.findPointerIndex(mMiniKeyboardTrackerId); - if (miniKeyboardPointerIndex >= 0 && miniKeyboardPointerIndex < pointerCount) { - final int miniKeyboardX = (int)me.getX(miniKeyboardPointerIndex); - final int miniKeyboardY = (int)me.getY(miniKeyboardPointerIndex); - MotionEvent translated = generateMiniKeyboardMotionEvent(action, - miniKeyboardX, miniKeyboardY, eventTime); - mMiniKeyboard.onTouchEvent(translated); - translated.recycle(); - } - return true; - } - - if (mHandler.isInKeyRepeat()) { - // It will keep being in the key repeating mode while the key is being pressed. - if (action == MotionEvent.ACTION_MOVE) { - return true; - } - final PointerTracker tracker = getPointerTracker(id); - // Key repeating timer will be canceled if 2 or more keys are in action, and current - // event (UP or DOWN) is non-modifier key. - if (pointerCount > 1 && !tracker.isModifier()) { - mHandler.cancelKeyRepeatTimer(); - } - // Up event will pass through. - } - - // TODO: cleanup this code into a multi-touch to single-touch event converter class? - // Translate mutli-touch event to single-touch events on the device that has no distinct - // multi-touch panel. - if (!mHasDistinctMultitouch) { - // Use only main (id=0) pointer tracker. - PointerTracker tracker = getPointerTracker(0); - int oldPointerCount = mOldPointerCount; - if (pointerCount == 1 && oldPointerCount == 2) { - // Multi-touch to single touch transition. - // Send a down event for the latest pointer. - tracker.onDownEvent(x, y, eventTime); - } else if (pointerCount == 2 && oldPointerCount == 1) { - // Single-touch to multi-touch transition. - // Send an up event for the last pointer. - tracker.onUpEvent(tracker.getLastX(), tracker.getLastY(), eventTime); - } else if (pointerCount == 1 && oldPointerCount == 1) { - tracker.onTouchEvent(action, x, y, eventTime); - } else { - Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount - + " (old " + oldPointerCount + ")"); - } - mOldPointerCount = pointerCount; - return true; - } - - if (action == MotionEvent.ACTION_MOVE) { - for (int i = 0; i < pointerCount; i++) { - PointerTracker tracker = getPointerTracker(me.getPointerId(i)); - tracker.onMoveEvent((int)me.getX(i), (int)me.getY(i), eventTime); - } - } else { - PointerTracker tracker = getPointerTracker(id); - switch (action) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - onDownEvent(tracker, x, y, eventTime); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - onUpEvent(tracker, x, y, eventTime); - break; - case MotionEvent.ACTION_CANCEL: - onCancelEvent(tracker, x, y, eventTime); - break; - } - } - - return true; - } - - private void onDownEvent(PointerTracker tracker, int x, int y, long eventTime) { - if (tracker.isOnModifierKey(x, y)) { - // Before processing a down event of modifier key, all pointers already being tracked - // should be released. - mPointerQueue.releaseAllPointersExcept(null, eventTime); - } - tracker.onDownEvent(x, y, eventTime); - mPointerQueue.add(tracker); - } - - private void onUpEvent(PointerTracker tracker, int x, int y, long eventTime) { - if (tracker.isModifier()) { - // Before processing an up event of modifier key, all pointers already being tracked - // should be released. - mPointerQueue.releaseAllPointersExcept(tracker, eventTime); - } else { - int index = mPointerQueue.lastIndexOf(tracker); - if (index >= 0) { - mPointerQueue.releaseAllPointersOlderThan(tracker, eventTime); - } else { - Log.w(TAG, "onUpEvent: corresponding down event not found for pointer " - + tracker.mPointerId); - } - } - tracker.onUpEvent(x, y, eventTime); - mPointerQueue.remove(tracker); - } - - private void onCancelEvent(PointerTracker tracker, int x, int y, long eventTime) { - tracker.onCancelEvent(x, y, eventTime); - mPointerQueue.remove(tracker); - } - - protected void swipeRight() { - mKeyboardActionListener.swipeRight(); - } - - protected void swipeLeft() { - mKeyboardActionListener.swipeLeft(); - } - - protected void swipeUp() { - mKeyboardActionListener.swipeUp(); - } - - protected void swipeDown() { - mKeyboardActionListener.swipeDown(); - } - - public void closing() { - mPreviewPopup.dismiss(); - mHandler.cancelAllMessages(); - - dismissPopupKeyboard(); - mBuffer = null; - mCanvas = null; - mMiniKeyboardCache.clear(); - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - closing(); - } - - private void dismissPopupKeyboard() { - if (mMiniKeyboardPopup.isShowing()) { - mMiniKeyboardPopup.dismiss(); - mMiniKeyboard = null; - mMiniKeyboardOriginX = 0; - mMiniKeyboardOriginY = 0; - invalidateAllKeys(); - } - } - - public boolean handleBack() { - if (mMiniKeyboardPopup.isShowing()) { - dismissPopupKeyboard(); - return true; - } - return false; - } -} diff --git a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java index fc517aeb7..ad3f1db9b 100644 --- a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java +++ b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java @@ -57,6 +57,7 @@ public class InputLanguageSelection extends PreferenceActivity { return this.label; } + @Override public int compareTo(Object o) { return sCollator.compare(this.label, ((Loc) o).label); } diff --git a/java/src/com/android/inputmethod/latin/KeyDetector.java b/java/src/com/android/inputmethod/latin/KeyDetector.java deleted file mode 100644 index 600a12fe5..000000000 --- a/java/src/com/android/inputmethod/latin/KeyDetector.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * 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.latin; - -import com.android.inputmethod.latin.BaseKeyboard.Key; - -import java.util.Arrays; -import java.util.List; - -abstract class KeyDetector { - protected BaseKeyboard mKeyboard; - - private Key[] mKeys; - - protected int mCorrectionX; - - protected int mCorrectionY; - - protected boolean mProximityCorrectOn; - - protected int mProximityThresholdSquare; - - public Key[] setKeyboard(BaseKeyboard keyboard, float correctionX, float correctionY) { - if (keyboard == null) - throw new NullPointerException(); - mCorrectionX = (int)correctionX; - mCorrectionY = (int)correctionY; - mKeyboard = keyboard; - List keys = mKeyboard.getKeys(); - Key[] array = keys.toArray(new Key[keys.size()]); - mKeys = array; - return array; - } - - protected int getTouchX(int x) { - return x + mCorrectionX; - } - - protected int getTouchY(int y) { - return y + mCorrectionY; - } - - protected Key[] getKeys() { - if (mKeys == null) - throw new IllegalStateException("keyboard isn't set"); - // mKeyboard is guaranteed not to be null at setKeybaord() method if mKeys is not null - return mKeys; - } - - public void setProximityCorrectionEnabled(boolean enabled) { - mProximityCorrectOn = enabled; - } - - public boolean isProximityCorrectionEnabled() { - return mProximityCorrectOn; - } - - public void setProximityThreshold(int threshold) { - mProximityThresholdSquare = threshold * threshold; - } - - /** - * Allocates array that can hold all key indices returned by {@link #getKeyIndexAndNearbyCodes} - * method. The maximum size of the array should be computed by {@link #getMaxNearbyKeys}. - * - * @return Allocates and returns an array that can hold all key indices returned by - * {@link #getKeyIndexAndNearbyCodes} method. All elements in the returned array are - * initialized by {@link com.android.inputmethod.latin.LatinKeyboardView.NOT_A_KEY} - * value. - */ - public int[] newCodeArray() { - int[] codes = new int[getMaxNearbyKeys()]; - Arrays.fill(codes, BaseKeyboardView.NOT_A_KEY); - return codes; - } - - /** - * Computes maximum size of the array that can contain all nearby key indices returned by - * {@link #getKeyIndexAndNearbyCodes}. - * - * @return Returns maximum size of the array that can contain all nearby key indices returned - * by {@link #getKeyIndexAndNearbyCodes}. - */ - abstract protected int getMaxNearbyKeys(); - - /** - * Finds all possible nearby key indices around a touch event point and returns the nearest key - * index. The algorithm to determine the nearby keys depends on the threshold set by - * {@link #setProximityThreshold(int)} and the mode set by - * {@link #setProximityCorrectionEnabled(boolean)}. - * - * @param x The x-coordinate of a touch point - * @param y The y-coordinate of a touch point - * @param allKeys All nearby key indices are returned in this array - * @return The nearest key index - */ - abstract public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys); -} diff --git a/java/src/com/android/inputmethod/latin/KeyStyles.java b/java/src/com/android/inputmethod/latin/KeyStyles.java deleted file mode 100644 index fceede7c3..000000000 --- a/java/src/com/android/inputmethod/latin/KeyStyles.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * 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.latin; - -import com.android.inputmethod.latin.BaseKeyboardParser.ParseException; - -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.graphics.drawable.Drawable; -import android.util.Log; -import android.util.TypedValue; - -import java.util.HashMap; -import java.util.StringTokenizer; - -public class KeyStyles { - private static final String TAG = "KeyStyles"; - - private final HashMap mStyles = - new HashMap(); - private static final KeyStyle EMPTY_KEY_STYLE = new EmptyKeyStyle(); - - public interface KeyStyle { - public int[] getIntArray(TypedArray a, int index); - public Drawable getDrawable(TypedArray a, int index); - public CharSequence getText(TypedArray a, int index); - public int getResourceId(TypedArray a, int index, int defaultValue); - public int getInt(TypedArray a, int index, int defaultValue); - public int getFlag(TypedArray a, int index, int defaultValue); - public boolean getBoolean(TypedArray a, int index, boolean defaultValue); - } - - public static class EmptyKeyStyle implements KeyStyle { - private EmptyKeyStyle() { - } - - public int[] getIntArray(TypedArray a, int index) { - return parseIntArray(a, index); - } - - public Drawable getDrawable(TypedArray a, int index) { - return a.getDrawable(index); - } - - public CharSequence getText(TypedArray a, int index) { - return a.getText(index); - } - - public int getResourceId(TypedArray a, int index, int defaultValue) { - return a.getResourceId(index, defaultValue); - } - - public int getInt(TypedArray a, int index, int defaultValue) { - return a.getInt(index, defaultValue); - } - - public int getFlag(TypedArray a, int index, int defaultValue) { - return a.getInt(index, defaultValue); - } - - public boolean getBoolean(TypedArray a, int index, boolean defaultValue) { - return a.getBoolean(index, defaultValue); - } - - protected static int[] parseIntArray(TypedArray a, int index) { - TypedValue v = new TypedValue(); - a.getValue(index, v); - if (v.type == TypedValue.TYPE_INT_DEC || v.type == TypedValue.TYPE_INT_HEX) { - return new int[] { v.data }; - } else if (v.type == TypedValue.TYPE_STRING) { - return parseCSV(v.string.toString()); - } else { - return null; - } - } - - private static int[] parseCSV(String value) { - int count = 0; - int lastIndex = 0; - if (value.length() > 0) { - count++; - while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) { - count++; - } - } - int[] values = new int[count]; - count = 0; - StringTokenizer st = new StringTokenizer(value, ","); - while (st.hasMoreTokens()) { - try { - values[count++] = Integer.parseInt(st.nextToken()); - } catch (NumberFormatException nfe) { - Log.w(TAG, "Error parsing integer CSV " + value); - } - } - return values; - } - } - - public static class DeclaredKeyStyle extends EmptyKeyStyle { - private final HashMap mAttributes = new HashMap(); - - @Override - public int[] getIntArray(TypedArray a, int index) { - return a.hasValue(index) - ? super.getIntArray(a, index) : (int[])mAttributes.get(index); - } - - @Override - public Drawable getDrawable(TypedArray a, int index) { - return a.hasValue(index) - ? super.getDrawable(a, index) : (Drawable)mAttributes.get(index); - } - - @Override - public CharSequence getText(TypedArray a, int index) { - return a.hasValue(index) - ? super.getText(a, index) : (CharSequence)mAttributes.get(index); - } - - @Override - public int getResourceId(TypedArray a, int index, int defaultValue) { - final Integer value = (Integer)mAttributes.get(index); - return super.getResourceId(a, index, (value != null) ? value : defaultValue); - } - - @Override - public int getFlag(TypedArray a, int index, int defaultValue) { - final Integer value = (Integer)mAttributes.get(index); - return super.getFlag(a, index, defaultValue) | (value != null ? value : 0); - } - - @Override - public boolean getBoolean(TypedArray a, int index, boolean defaultValue) { - final Boolean value = (Boolean)mAttributes.get(index); - return super.getBoolean(a, index, (value != null) ? value : defaultValue); - } - - private DeclaredKeyStyle() { - super(); - } - - private void parseKeyStyleAttributes(TypedArray a) { - // TODO: Currently not all Key attributes can be declared as style. - readIntArray(a, R.styleable.BaseKeyboard_Key_codes); - readText(a, R.styleable.BaseKeyboard_Key_keyLabel); - readFlag(a, R.styleable.BaseKeyboard_Key_keyLabelOption); - readText(a, R.styleable.BaseKeyboard_Key_keyOutputText); - readDrawable(a, R.styleable.BaseKeyboard_Key_keyIcon); - readDrawable(a, R.styleable.BaseKeyboard_Key_iconPreview); - readDrawable(a, R.styleable.BaseKeyboard_Key_keyHintIcon); - readDrawable(a, R.styleable.BaseKeyboard_Key_shiftedIcon); - readResourceId(a, R.styleable.BaseKeyboard_Key_popupKeyboard); - readBoolean(a, R.styleable.BaseKeyboard_Key_isModifier); - readBoolean(a, R.styleable.BaseKeyboard_Key_isSticky); - readBoolean(a, R.styleable.BaseKeyboard_Key_isRepeatable); - } - - private void readDrawable(TypedArray a, int index) { - if (a.hasValue(index)) - mAttributes.put(index, a.getDrawable(index)); - } - - private void readText(TypedArray a, int index) { - if (a.hasValue(index)) - mAttributes.put(index, a.getText(index)); - } - - private void readResourceId(TypedArray a, int index) { - if (a.hasValue(index)) - mAttributes.put(index, a.getResourceId(index, 0)); - } - - private void readFlag(TypedArray a, int index) { - final Integer value = (Integer)mAttributes.get(index); - if (a.hasValue(index)) - mAttributes.put(index, a.getInt(index, 0) | (value != null ? value : 0)); - } - - private void readBoolean(TypedArray a, int index) { - if (a.hasValue(index)) - mAttributes.put(index, a.getBoolean(index, false)); - } - - private void readIntArray(TypedArray a, int index) { - if (a.hasValue(index)) { - final int[] value = parseIntArray(a, index); - if (value != null) - mAttributes.put(index, value); - } - } - - private void addParent(DeclaredKeyStyle parentStyle) { - mAttributes.putAll(parentStyle.mAttributes); - } - } - - public void parseKeyStyleAttributes(TypedArray a, TypedArray keyAttrs, - XmlResourceParser parser) { - String styleName = a.getString(R.styleable.BaseKeyboard_KeyStyle_styleName); - if (mStyles.containsKey(styleName)) - throw new ParseException("duplicate key style declared: " + styleName, parser); - - final DeclaredKeyStyle style = new DeclaredKeyStyle(); - if (a.hasValue(R.styleable.BaseKeyboard_KeyStyle_parentStyle)) { - String parentStyle = a.getString( - R.styleable.BaseKeyboard_KeyStyle_parentStyle); - final DeclaredKeyStyle parent = mStyles.get(parentStyle); - if (parent == null) - throw new ParseException("Unknown parentStyle " + parent, parser); - style.addParent(parent); - } - style.parseKeyStyleAttributes(keyAttrs); - mStyles.put(styleName, style); - } - - public KeyStyle getKeyStyle(String styleName) { - return mStyles.get(styleName); - } - - public KeyStyle getEmptyKeyStyle() { - return EMPTY_KEY_STYLE; - } -} diff --git a/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java b/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java index 00b6f0a43..e8487e798 100644 --- a/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java @@ -16,16 +16,19 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.keyboard.KeyboardId; +import com.android.inputmethod.keyboard.LatinKeyboard; +import com.android.inputmethod.keyboard.LatinKeyboardView; + import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.util.Log; import android.view.InflateException; -import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import java.lang.ref.SoftReference; -import java.util.Arrays; import java.util.HashMap; import java.util.Locale; @@ -34,14 +37,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha private static final boolean DEBUG = false; public static final boolean DEBUG_STATE = false; - public static final int MODE_TEXT = 0; - public static final int MODE_URL = 1; - public static final int MODE_EMAIL = 2; - public static final int MODE_IM = 3; - public static final int MODE_WEB = 4; - public static final int MODE_PHONE = 5; - public static final int MODE_NUMBER = 6; - // Changing DEFAULT_LAYOUT_ID also requires prefs_for_debug.xml to be matched with. public static final String DEFAULT_LAYOUT_ID = "5"; public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20100902"; @@ -74,7 +69,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha private final HashMap> mKeyboardCache = new HashMap>(); - private int mMode = MODE_TEXT; /* default value */ + private int mMode = KeyboardId.MODE_TEXT; /* default value */ private int mImeOptions; private boolean mIsSymbols; /** mIsAutoCompletionActive indicates that auto completed word will be input instead of @@ -132,129 +127,13 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha // mSymbolsId and mSymbolsShiftedId to "phone keyboard" and "phone symbols keyboard" // respectively here for xlarge device's layout switching. mSymbolsId = new KeyboardId(locale, orientation, mode, - mode == MODE_PHONE ? R.xml.kbd_phone : R.xml.kbd_symbols, + mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone : R.xml.kbd_symbols, colorScheme, hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true); mSymbolsShiftedId = new KeyboardId(locale, orientation, mode, - mode == MODE_PHONE ? R.xml.kbd_phone_symbols : R.xml.kbd_symbols_shift, + mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone_symbols : R.xml.kbd_symbols_shift, colorScheme, hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true); } - /** - * Represents the parameters necessary to construct a new LatinKeyboard, - * which also serve as a unique identifier for each keyboard type. - */ - public static class KeyboardId { - public final Locale mLocale; - public final int mOrientation; - public final int mMode; - public final int mXmlId; - public final int mColorScheme; - public final boolean mHasSettingsKey; - public final boolean mVoiceKeyEnabled; - public final boolean mHasVoiceKey; - public final int mImeOptions; - public final boolean mEnableShiftLock; - - private final int mHashCode; - - public KeyboardId(Locale locale, int orientation, int mode, - int xmlId, int colorScheme, boolean hasSettingsKey, boolean voiceKeyEnabled, - boolean hasVoiceKey, int imeOptions, boolean enableShiftLock) { - this.mLocale = locale; - this.mOrientation = orientation; - this.mMode = mode; - this.mXmlId = xmlId; - this.mColorScheme = colorScheme; - this.mHasSettingsKey = hasSettingsKey; - this.mVoiceKeyEnabled = voiceKeyEnabled; - this.mHasVoiceKey = hasVoiceKey; - // We are interested only in IME_MASK_ACTION enum value and IME_FLAG_NO_ENTER_ACTION. - this.mImeOptions = imeOptions - & (EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION); - this.mEnableShiftLock = enableShiftLock; - - this.mHashCode = Arrays.hashCode(new Object[] { - locale, - orientation, - mode, - xmlId, - colorScheme, - hasSettingsKey, - voiceKeyEnabled, - hasVoiceKey, - imeOptions, - enableShiftLock, - }); - } - - public int getXmlId() { - return mXmlId; - } - - public boolean isAlphabetMode() { - return mXmlId == R.xml.kbd_qwerty; - } - - @Override - public boolean equals(Object other) { - return other instanceof KeyboardId && equals((KeyboardId) other); - } - - private boolean equals(KeyboardId other) { - return other.mLocale.equals(this.mLocale) - && other.mOrientation == this.mOrientation - && other.mMode == this.mMode - && other.mXmlId == this.mXmlId - && other.mColorScheme == this.mColorScheme - && other.mHasSettingsKey == this.mHasSettingsKey - && other.mVoiceKeyEnabled == this.mVoiceKeyEnabled - && other.mHasVoiceKey == this.mHasVoiceKey - && other.mImeOptions == this.mImeOptions - && other.mEnableShiftLock == this.mEnableShiftLock; - } - - @Override - public int hashCode() { - return mHashCode; - } - - @Override - public String toString() { - return String.format("[%s %s %5s imeOptions=0x%08x xml=0x%08x %s%s%s%s%s]", - mLocale, - (mOrientation == 1 ? "port" : "land"), - modeName(mMode), - mImeOptions, - mXmlId, - colorSchemeName(mColorScheme), - (mHasSettingsKey ? " hasSettingsKey" : ""), - (mVoiceKeyEnabled ? " voiceKeyEnabled" : ""), - (mHasVoiceKey ? " hasVoiceKey" : ""), - (mEnableShiftLock ? " enableShiftLock" : "")); - } - - private static String modeName(int mode) { - switch (mode) { - case MODE_TEXT: return "text"; - case MODE_URL: return "url"; - case MODE_EMAIL: return "email"; - case MODE_IM: return "im"; - case MODE_WEB: return "web"; - case MODE_PHONE: return "phone"; - case MODE_NUMBER: return "number"; - } - return null; - } - - private static String colorSchemeName(int colorScheme) { - switch (colorScheme) { - case BaseKeyboardView.COLOR_SCHEME_WHITE: return "white"; - case BaseKeyboardView.COLOR_SCHEME_BLACK: return "black"; - } - return null; - } - } - private boolean hasVoiceKey(boolean isSymbols) { return mVoiceKeyEnabled && (isSymbols != mVoiceButtonOnPrimary); } @@ -327,9 +206,9 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha final boolean enableShiftLock; if (isSymbols) { - if (mode == MODE_PHONE) { + if (mode == KeyboardId.MODE_PHONE) { xmlId = R.xml.kbd_phone_symbols; - } else if (mode == MODE_NUMBER) { + } else if (mode == KeyboardId.MODE_NUMBER) { // Note: MODE_NUMBER keyboard layout has no "switch alpha symbol" key. xmlId = R.xml.kbd_number; } else { @@ -337,10 +216,10 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha } enableShiftLock = false; } else { - if (mode == MODE_PHONE) { + if (mode == KeyboardId.MODE_PHONE) { xmlId = R.xml.kbd_phone; enableShiftLock = false; - } else if (mode == MODE_NUMBER) { + } else if (mode == KeyboardId.MODE_NUMBER) { xmlId = R.xml.kbd_number; enableShiftLock = false; } else { @@ -731,7 +610,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha private int getColorScheme() { return (mInputView != null) - ? mInputView.getColorScheme() : BaseKeyboardView.COLOR_SCHEME_WHITE; + ? mInputView.getColorScheme() : KeyboardView.COLOR_SCHEME_WHITE; } public void onAutoCompletionStateChanged(boolean isAutoCompletion) { diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 36c77efaf..4f4112fa8 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -16,6 +16,12 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardActionListener; +import com.android.inputmethod.keyboard.KeyboardId; +import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.keyboard.LatinKeyboard; +import com.android.inputmethod.keyboard.LatinKeyboardView; import com.android.inputmethod.latin.LatinIMEUtil.RingCharBuffer; import com.android.inputmethod.voice.VoiceIMEConnector; @@ -73,7 +79,7 @@ import java.util.Locale; * Input method implementation for Qwerty'ish keyboard. */ public class LatinIME extends InputMethodService - implements BaseKeyboardView.OnKeyboardActionListener, + implements KeyboardActionListener, SharedPreferences.OnSharedPreferenceChangeListener, Tutorial.TutorialListener { private static final String TAG = "LatinIME"; @@ -548,10 +554,10 @@ public class LatinIME extends InputMethodService switch (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) { case EditorInfo.TYPE_CLASS_NUMBER: case EditorInfo.TYPE_CLASS_DATETIME: - mode = KeyboardSwitcher.MODE_NUMBER; + mode = KeyboardId.MODE_NUMBER; break; case EditorInfo.TYPE_CLASS_PHONE: - mode = KeyboardSwitcher.MODE_PHONE; + mode = KeyboardId.MODE_PHONE; break; case EditorInfo.TYPE_CLASS_TEXT: //startPrediction(); @@ -568,24 +574,24 @@ public class LatinIME extends InputMethodService } if (isEmailVariation(variation)) { mPredictionOn = false; - mode = KeyboardSwitcher.MODE_EMAIL; + mode = KeyboardId.MODE_EMAIL; } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) { mPredictionOn = false; - mode = KeyboardSwitcher.MODE_URL; + mode = KeyboardId.MODE_URL; } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) { - mode = KeyboardSwitcher.MODE_IM; + mode = KeyboardId.MODE_IM; } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) { mPredictionOn = false; - mode = KeyboardSwitcher.MODE_TEXT; + mode = KeyboardId.MODE_TEXT; } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) { - mode = KeyboardSwitcher.MODE_WEB; + mode = KeyboardId.MODE_WEB; // If it's a browser edit field and auto correct is not ON explicitly, then // disable auto correction, but keep suggestions on. if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) { mInputTypeNoAutoCorrect = true; } } else { - mode = KeyboardSwitcher.MODE_TEXT; + mode = KeyboardId.MODE_TEXT; } // If NO_SUGGESTIONS is set, don't do prediction. @@ -604,7 +610,7 @@ public class LatinIME extends InputMethodService } break; default: - mode = KeyboardSwitcher.MODE_TEXT; + mode = KeyboardId.MODE_TEXT; break; } inputView.closing(); @@ -675,7 +681,7 @@ public class LatinIME extends InputMethodService mVoiceConnector.flushVoiceInputLogs(mConfigurationChanging); - BaseKeyboardView inputView = mKeyboardSwitcher.getInputView(); + KeyboardView inputView = mKeyboardSwitcher.getInputView(); if (inputView != null) inputView.closing(); if (mAutoDictionary != null) mAutoDictionary.flushPendingWrites(); @@ -685,7 +691,7 @@ public class LatinIME extends InputMethodService @Override public void onFinishInputView(boolean finishingInput) { super.onFinishInputView(finishingInput); - BaseKeyboardView inputView = mKeyboardSwitcher.getInputView(); + KeyboardView inputView = mKeyboardSwitcher.getInputView(); if (inputView != null) inputView.setForeground(false); // Remove pending messages related to update suggestions @@ -1073,51 +1079,52 @@ public class LatinIME extends InputMethodService // Implementation of KeyboardViewListener + @Override public void onKey(int primaryCode, int[] keyCodes, int x, int y) { long when = SystemClock.uptimeMillis(); - if (primaryCode != BaseKeyboard.KEYCODE_DELETE || when > mLastKeyTime + QUICK_PRESS) { + if (primaryCode != Keyboard.KEYCODE_DELETE || when > mLastKeyTime + QUICK_PRESS) { mDeleteCount = 0; } mLastKeyTime = when; KeyboardSwitcher switcher = mKeyboardSwitcher; final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); switch (primaryCode) { - case BaseKeyboard.KEYCODE_DELETE: + case Keyboard.KEYCODE_DELETE: handleBackspace(); mDeleteCount++; LatinImeLogger.logOnDelete(); break; - case BaseKeyboard.KEYCODE_SHIFT: + case Keyboard.KEYCODE_SHIFT: // Shift key is handled in onPress() when device has distinct multi-touch panel. if (!distinctMultiTouch) switcher.toggleShift(); break; - case BaseKeyboard.KEYCODE_MODE_CHANGE: + case Keyboard.KEYCODE_MODE_CHANGE: // Symbol key is handled in onPress() when device has distinct multi-touch panel. if (!distinctMultiTouch) switcher.changeKeyboardMode(); break; - case BaseKeyboard.KEYCODE_CANCEL: + case Keyboard.KEYCODE_CANCEL: if (!isShowingOptionDialog()) { handleClose(); } break; - case LatinKeyboardView.KEYCODE_OPTIONS: + case LatinKeyboard.KEYCODE_OPTIONS: onOptionKeyPressed(); break; - case LatinKeyboardView.KEYCODE_OPTIONS_LONGPRESS: + case LatinKeyboard.KEYCODE_OPTIONS_LONGPRESS: onOptionKeyLongPressed(); break; - case LatinKeyboardView.KEYCODE_NEXT_LANGUAGE: + case LatinKeyboard.KEYCODE_NEXT_LANGUAGE: toggleLanguage(false, true); break; - case LatinKeyboardView.KEYCODE_PREV_LANGUAGE: + case LatinKeyboard.KEYCODE_PREV_LANGUAGE: toggleLanguage(false, false); break; - case LatinKeyboardView.KEYCODE_CAPSLOCK: + case LatinKeyboard.KEYCODE_CAPSLOCK: switcher.toggleCapsLock(); break; - case LatinKeyboardView.KEYCODE_VOICE: /* was a button press, was not a swipe */ + case LatinKeyboard.KEYCODE_VOICE: /* was a button press, was not a swipe */ mVoiceConnector.startListening(false, mKeyboardSwitcher.getInputView().getWindowToken(), mConfigurationChanging); break; @@ -1143,6 +1150,7 @@ public class LatinIME extends InputMethodService mEnteredText = null; } + @Override public void onText(CharSequence text) { mVoiceConnector.commitVoiceInput(); InputConnection ic = getCurrentInputConnection(); @@ -1161,6 +1169,7 @@ public class LatinIME extends InputMethodService mEnteredText = text; } + @Override public void onCancel() { // User released a finger outside any key } @@ -1420,24 +1429,26 @@ public class LatinIME extends InputMethodService } public void switchToKeyboardView() { - mHandler.post(new Runnable() { - public void run() { - if (DEBUG) { - Log.d(TAG, "Switch to keyboard view."); - } - View v = mKeyboardSwitcher.getInputView(); - if (v != null) { - // Confirms that the keyboard view doesn't have parent view. - ViewParent p = v.getParent(); - if (p != null && p instanceof ViewGroup) { - ((ViewGroup)p).removeView(v); - } - setInputView(v); - } - setCandidatesViewShown(isCandidateStripVisible()); - updateInputViewShown(); - mHandler.postUpdateSuggestions(); - }}); + mHandler.post(new Runnable() { + @Override + public void run() { + if (DEBUG) { + Log.d(TAG, "Switch to keyboard view."); + } + View v = mKeyboardSwitcher.getInputView(); + if (v != null) { + // Confirms that the keyboard view doesn't have parent view. + ViewParent p = v.getParent(); + if (p != null && p instanceof ViewGroup) { + ((ViewGroup) p).removeView(v); + } + setInputView(v); + } + setCandidatesViewShown(isCandidateStripVisible()); + updateInputViewShown(); + mHandler.postUpdateSuggestions(); + } + }); } public void clearSuggestions() { @@ -1586,8 +1597,8 @@ public class LatinIME extends InputMethodService LatinImeLogger.logOnManualSuggestion( "", suggestion.toString(), index, suggestions); final char primaryCode = suggestion.charAt(0); - onKey(primaryCode, new int[]{primaryCode}, BaseKeyboardView.NOT_A_TOUCH_COORDINATE, - BaseKeyboardView.NOT_A_TOUCH_COORDINATE); + onKey(primaryCode, new int[]{primaryCode}, KeyboardView.NOT_A_TOUCH_COORDINATE, + KeyboardView.NOT_A_TOUCH_COORDINATE); if (ic != null) { ic.endBatchEdit(); } @@ -1877,6 +1888,7 @@ public class LatinIME extends InputMethodService switcher.updateShiftState(); } + @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { mSubtypeSwitcher.onSharedPreferenceChanged(sharedPreferences, key); @@ -1888,6 +1900,7 @@ public class LatinIME extends InputMethodService } } + @Override public void swipeRight() { if (LatinKeyboardView.DEBUG_AUTO_PLAY) { CharSequence text = ((android.text.ClipboardManager)getSystemService( @@ -1898,38 +1911,43 @@ public class LatinIME extends InputMethodService } } + @Override public void swipeLeft() { } + @Override public void swipeDown() { handleClose(); } + @Override public void swipeUp() { } + @Override public void onPress(int primaryCode) { vibrate(); playKeyClick(primaryCode); KeyboardSwitcher switcher = mKeyboardSwitcher; final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); - if (distinctMultiTouch && primaryCode == BaseKeyboard.KEYCODE_SHIFT) { + if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_SHIFT) { switcher.onPressShift(); - } else if (distinctMultiTouch && primaryCode == BaseKeyboard.KEYCODE_MODE_CHANGE) { + } else if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_MODE_CHANGE) { switcher.onPressSymbol(); } else { switcher.onOtherKeyPressed(); } } + @Override public void onRelease(int primaryCode) { KeyboardSwitcher switcher = mKeyboardSwitcher; // Reset any drag flags in the keyboard switcher.keyReleased(); final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); - if (distinctMultiTouch && primaryCode == BaseKeyboard.KEYCODE_SHIFT) { + if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_SHIFT) { switcher.onReleaseShift(); - } else if (distinctMultiTouch && primaryCode == BaseKeyboard.KEYCODE_MODE_CHANGE) { + } else if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_MODE_CHANGE) { switcher.onReleaseSymbol(); } } @@ -1966,7 +1984,7 @@ public class LatinIME extends InputMethodService // FIXME: These should be triggered after auto-repeat logic int sound = AudioManager.FX_KEYPRESS_STANDARD; switch (primaryCode) { - case BaseKeyboard.KEYCODE_DELETE: + case Keyboard.KEYCODE_DELETE: sound = AudioManager.FX_KEYPRESS_DELETE; break; case KEYCODE_ENTER: @@ -2006,6 +2024,7 @@ public class LatinIME extends InputMethodService } // Tutorial.TutorialListener + @Override public void onTutorialDone() { sendDownUpKeyEvents(-1); // Inform the setupwizard that tutorial is in last bubble mTutorial = null; @@ -2171,6 +2190,7 @@ public class LatinIME extends InputMethodService itemInputMethod, itemSettings}, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface di, int position) { di.dismiss(); switch (position) { diff --git a/java/src/com/android/inputmethod/latin/LatinIMEDebugSettings.java b/java/src/com/android/inputmethod/latin/LatinIMEDebugSettings.java index cba1a0af9..68738eca1 100644 --- a/java/src/com/android/inputmethod/latin/LatinIMEDebugSettings.java +++ b/java/src/com/android/inputmethod/latin/LatinIMEDebugSettings.java @@ -43,6 +43,7 @@ public class LatinIMEDebugSettings extends PreferenceActivity updateDebugMode(); } + @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { if (key.equals(DEBUG_MODE_KEY)) { if (mDebugMode != null) { diff --git a/java/src/com/android/inputmethod/latin/LatinIMESettings.java b/java/src/com/android/inputmethod/latin/LatinIMESettings.java index ae6646113..187b6d394 100644 --- a/java/src/com/android/inputmethod/latin/LatinIMESettings.java +++ b/java/src/com/android/inputmethod/latin/LatinIMESettings.java @@ -137,6 +137,7 @@ public class LatinIMESettings extends PreferenceActivity super.onDestroy(); } + @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { (new BackupManager(this)).dataChanged(); // If turning on voice input, show dialog @@ -181,6 +182,7 @@ public class LatinIMESettings extends PreferenceActivity switch (id) { case VOICE_INPUT_CONFIRM_DIALOG: DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { if (whichButton == DialogInterface.BUTTON_NEGATIVE) { mVoicePreference.setValue(mVoiceModeOff); @@ -226,6 +228,7 @@ public class LatinIMESettings extends PreferenceActivity } } + @Override public void onDismiss(DialogInterface dialog) { mLogger.settingsWarningDialogDismissed(); if (!mOkClicked) { diff --git a/java/src/com/android/inputmethod/latin/LatinIMEUtil.java b/java/src/com/android/inputmethod/latin/LatinIMEUtil.java index a58f630cd..f508b9ab8 100644 --- a/java/src/com/android/inputmethod/latin/LatinIMEUtil.java +++ b/java/src/com/android/inputmethod/latin/LatinIMEUtil.java @@ -310,6 +310,7 @@ public class LatinIMEUtil { public void write(final String log) { mLoggingHandler.post(new Runnable() { + @Override public void run() { createLogFileIfNotExist(); final long currentTime = System.currentTimeMillis(); @@ -327,6 +328,7 @@ public class LatinIMEUtil { public void printAll() { mLoggingHandler.post(new Runnable() { + @Override public void run() { mWriter.flush(); StringBuilder sb = new StringBuilder(); @@ -355,6 +357,7 @@ public class LatinIMEUtil { public void clearAll() { mLoggingHandler.post(new Runnable() { + @Override public void run() { if (mFile != null && mFile.exists()) { if (LatinImeLogger.sDBG) { diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java index b64e3ddb2..de194d21b 100644 --- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java +++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.Dictionary.DataType; import android.content.Context; @@ -27,6 +28,7 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang public static boolean sDBG = false; + @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { } @@ -67,7 +69,7 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang public static void onAddSuggestedWord(String word, int typeId, DataType dataType) { } - public static void onSetKeyboard(BaseKeyboard kb) { + public static void onSetKeyboard(Keyboard kb) { } public static void onPrintAllUsabilityStudtyLogs() { diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboard.java b/java/src/com/android/inputmethod/latin/LatinKeyboard.java deleted file mode 100644 index cae0b10b3..000000000 --- a/java/src/com/android/inputmethod/latin/LatinKeyboard.java +++ /dev/null @@ -1,720 +0,0 @@ -/* - * Copyright (C) 2008 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.latin; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.PixelFormat; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.text.TextPaint; -import android.util.Log; -import android.view.ViewConfiguration; - -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -public class LatinKeyboard extends BaseKeyboard { - - private static final boolean DEBUG_PREFERRED_LETTER = false; - private static final String TAG = "LatinKeyboard"; - private static final int OPACITY_FULLY_OPAQUE = 255; - private static final int SPACE_LED_LENGTH_PERCENT = 80; - - private Drawable mShiftLockPreviewIcon; - private final HashMap mNormalShiftIcons = new HashMap(); - private Drawable mSpaceIcon; - private Drawable mSpaceAutoCompletionIndicator; - private Drawable mSpacePreviewIcon; - private final Drawable mButtonArrowLeftIcon; - private final Drawable mButtonArrowRightIcon; - private final int mSpaceBarTextShadowColor; - private Key mSpaceKey; - private int mSpaceKeyIndex = -1; - private int mSpaceDragStartX; - private int mSpaceDragLastDiff; - private final Resources mRes; - private final Context mContext; - private boolean mCurrentlyInSpace; - private SlidingLocaleDrawable mSlidingLocaleIcon; - private int[] mPrefLetterFrequencies; - private int mPrefLetter; - private int mPrefLetterX; - private int mPrefLetterY; - private int mPrefDistance; - - private LatinKeyboardShiftState mShiftState = new LatinKeyboardShiftState(); - - private static final float SPACEBAR_DRAG_THRESHOLD = 0.8f; - private static final float OVERLAP_PERCENTAGE_LOW_PROB = 0.70f; - private static final float OVERLAP_PERCENTAGE_HIGH_PROB = 0.85f; - // Minimum width of space key preview (proportional to keyboard width) - private static final float SPACEBAR_POPUP_MIN_RATIO = 0.4f; - // Height in space key the language name will be drawn. (proportional to space key height) - private static final float SPACEBAR_LANGUAGE_BASELINE = 0.6f; - // If the full language name needs to be smaller than this value to be drawn on space key, - // its short language name will be used instead. - private static final float MINIMUM_SCALE_OF_LANGUAGE_NAME = 0.8f; - - private static int sSpacebarVerticalCorrection; - - public LatinKeyboard(Context context, KeyboardSwitcher.KeyboardId id) { - super(context, id); - final Resources res = context.getResources(); - mContext = context; - mRes = res; - if (id.mColorScheme == BaseKeyboardView.COLOR_SCHEME_BLACK) { - mSpaceBarTextShadowColor = res.getColor( - R.color.latinkeyboard_bar_language_shadow_black); - } else { // default color scheme is BaseKeyboardView.COLOR_SCHEME_WHITE - mSpaceBarTextShadowColor = res.getColor( - R.color.latinkeyboard_bar_language_shadow_white); - } - mShiftLockPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_shift_locked); - setDefaultBounds(mShiftLockPreviewIcon); - mSpaceAutoCompletionIndicator = res.getDrawable(R.drawable.sym_keyboard_space_led); - mButtonArrowLeftIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_left); - mButtonArrowRightIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_right); - sSpacebarVerticalCorrection = res.getDimensionPixelOffset( - R.dimen.spacebar_vertical_correction); - mSpaceKeyIndex = indexOf(LatinIME.KEYCODE_SPACE); - } - - @Override - protected Key createKeyFromXml(Resources res, Row parent, int x, int y, - XmlResourceParser parser, KeyStyles keyStyles) { - Key key = new LatinKey(res, parent, x, y, parser, keyStyles); - switch (key.codes[0]) { - case LatinIME.KEYCODE_SPACE: - mSpaceKey = key; - mSpaceIcon = key.icon; - mSpacePreviewIcon = key.iconPreview; - break; - } - - return key; - } - - public void enableShiftLock() { - for (final Key key : getShiftKeys()) { - if (key instanceof LatinKey) { - ((LatinKey)key).enableShiftLock(); - } - mNormalShiftIcons.put(key, key.icon); - } - } - - public boolean setShiftLocked(boolean newShiftLockState) { - final Map shiftedIcons = getShiftedIcons(); - for (final Key key : getShiftKeys()) { - key.on = newShiftLockState; - key.icon = newShiftLockState ? shiftedIcons.get(key) : mNormalShiftIcons.get(key); - } - mShiftState.setShiftLocked(newShiftLockState); - return true; - } - - public boolean isShiftLocked() { - return mShiftState.isShiftLocked(); - } - - @Override - public boolean setShifted(boolean newShiftState) { - if (getShiftKeys().size() == 0) - return super.setShifted(newShiftState); - - final Map shiftedIcons = getShiftedIcons(); - for (final Key key : getShiftKeys()) { - if (!newShiftState && !mShiftState.isShiftLocked()) { - key.icon = mNormalShiftIcons.get(key); - } else if (newShiftState && !mShiftState.isShiftedOrShiftLocked()) { - key.icon = shiftedIcons.get(key); - } - } - return mShiftState.setShifted(newShiftState); - } - - @Override - public boolean isShiftedOrShiftLocked() { - if (getShiftKeys().size() > 0) { - return mShiftState.isShiftedOrShiftLocked(); - } else { - return super.isShiftedOrShiftLocked(); - } - } - - public void setAutomaticTemporaryUpperCase() { - setShifted(true); - mShiftState.setAutomaticTemporaryUpperCase(); - } - - public boolean isAutomaticTemporaryUpperCase() { - return isAlphaKeyboard() && mShiftState.isAutomaticTemporaryUpperCase(); - } - - public boolean isManualTemporaryUpperCase() { - return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCase(); - } - - /* package */ LatinKeyboardShiftState getKeyboardShiftState() { - return mShiftState; - } - - public boolean isAlphaKeyboard() { - return mId.getXmlId() == R.xml.kbd_qwerty; - } - - public boolean isPhoneKeyboard() { - return mId.mMode == KeyboardSwitcher.MODE_PHONE; - } - - public boolean isNumberKeyboard() { - return mId.mMode == KeyboardSwitcher.MODE_NUMBER; - } - - /** - * @return a key which should be invalidated. - */ - public Key onAutoCompletionStateChanged(boolean isAutoCompletion) { - updateSpaceBarForLocale(isAutoCompletion); - return mSpaceKey; - } - - private void updateSpaceBarForLocale(boolean isAutoCompletion) { - final Resources res = mRes; - // If application locales are explicitly selected. - if (SubtypeSwitcher.getInstance().needsToDisplayLanguage()) { - mSpaceKey.icon = new BitmapDrawable(res, - drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCompletion)); - } else { - // sym_keyboard_space_led can be shared with Black and White symbol themes. - if (isAutoCompletion) { - mSpaceKey.icon = new BitmapDrawable(res, - drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCompletion)); - } else { - mSpaceKey.icon = mSpaceIcon; - } - } - } - - // Compute width of text with specified text size using paint. - private static int getTextWidth(Paint paint, String text, float textSize, Rect bounds) { - paint.setTextSize(textSize); - paint.getTextBounds(text, 0, text.length(), bounds); - return bounds.width(); - } - - // Layout local language name and left and right arrow on space bar. - private static String layoutSpaceBar(Paint paint, Locale locale, Drawable lArrow, - Drawable rArrow, int width, int height, float origTextSize, - boolean allowVariableTextSize) { - final float arrowWidth = lArrow.getIntrinsicWidth(); - final float arrowHeight = lArrow.getIntrinsicHeight(); - final float maxTextWidth = width - (arrowWidth + arrowWidth); - final Rect bounds = new Rect(); - - // Estimate appropriate language name text size to fit in maxTextWidth. - String language = SubtypeSwitcher.getDisplayLanguage(locale); - int textWidth = getTextWidth(paint, language, origTextSize, bounds); - // Assuming text width and text size are proportional to each other. - float textSize = origTextSize * Math.min(maxTextWidth / textWidth, 1.0f); - - final boolean useShortName; - if (allowVariableTextSize) { - textWidth = getTextWidth(paint, language, textSize, bounds); - // If text size goes too small or text does not fit, use short name - useShortName = textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME - || textWidth > maxTextWidth; - } else { - useShortName = textWidth > maxTextWidth; - textSize = origTextSize; - } - if (useShortName) { - language = SubtypeSwitcher.getShortDisplayLanguage(locale); - textWidth = getTextWidth(paint, language, origTextSize, bounds); - textSize = origTextSize * Math.min(maxTextWidth / textWidth, 1.0f); - } - paint.setTextSize(textSize); - - // Place left and right arrow just before and after language text. - final float baseline = height * SPACEBAR_LANGUAGE_BASELINE; - final int top = (int)(baseline - arrowHeight); - final float remains = (width - textWidth) / 2; - lArrow.setBounds((int)(remains - arrowWidth), top, (int)remains, (int)baseline); - rArrow.setBounds((int)(remains + textWidth), top, (int)(remains + textWidth + arrowWidth), - (int)baseline); - - return language; - } - - private Bitmap drawSpaceBar(int opacity, boolean isAutoCompletion) { - final int width = mSpaceKey.width; - final int height = mSpaceIcon.getIntrinsicHeight(); - final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(buffer); - final Resources res = mRes; - canvas.drawColor(res.getColor(R.color.latinkeyboard_transparent), PorterDuff.Mode.CLEAR); - - SubtypeSwitcher subtypeSwitcher = SubtypeSwitcher.getInstance(); - // If application locales are explicitly selected. - if (subtypeSwitcher.needsToDisplayLanguage()) { - final Paint paint = new Paint(); - paint.setAlpha(opacity); - paint.setAntiAlias(true); - paint.setTextAlign(Align.CENTER); - - final boolean allowVariableTextSize = true; - final String language = layoutSpaceBar(paint, subtypeSwitcher.getInputLocale(), - mButtonArrowLeftIcon, mButtonArrowRightIcon, width, height, - getTextSizeFromTheme(android.R.style.TextAppearance_Small, 14), - allowVariableTextSize); - - // Draw language text with shadow - final float baseline = height * SPACEBAR_LANGUAGE_BASELINE; - final float descent = paint.descent(); - paint.setColor(mSpaceBarTextShadowColor); - canvas.drawText(language, width / 2, baseline - descent - 1, paint); - paint.setColor(res.getColor(R.color.latinkeyboard_bar_language_text)); - canvas.drawText(language, width / 2, baseline - descent, paint); - - // Put arrows that are already layed out on either side of the text - if (SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCHER - && subtypeSwitcher.getEnabledKeyboardLocaleCount() > 1) { - mButtonArrowLeftIcon.draw(canvas); - mButtonArrowRightIcon.draw(canvas); - } - } - - // Draw the spacebar icon at the bottom - if (isAutoCompletion) { - final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100; - final int iconHeight = mSpaceAutoCompletionIndicator.getIntrinsicHeight(); - int x = (width - iconWidth) / 2; - int y = height - iconHeight; - mSpaceAutoCompletionIndicator.setBounds(x, y, x + iconWidth, y + iconHeight); - mSpaceAutoCompletionIndicator.draw(canvas); - } else { - final int iconWidth = mSpaceIcon.getIntrinsicWidth(); - final int iconHeight = mSpaceIcon.getIntrinsicHeight(); - int x = (width - iconWidth) / 2; - int y = height - iconHeight; - mSpaceIcon.setBounds(x, y, x + iconWidth, y + iconHeight); - mSpaceIcon.draw(canvas); - } - return buffer; - } - - private void updateLocaleDrag(int diff) { - if (mSlidingLocaleIcon == null) { - final int width = Math.max(mSpaceKey.width, - (int)(getMinWidth() * SPACEBAR_POPUP_MIN_RATIO)); - final int height = mSpacePreviewIcon.getIntrinsicHeight(); - mSlidingLocaleIcon = new SlidingLocaleDrawable(mSpacePreviewIcon, width, height); - mSlidingLocaleIcon.setBounds(0, 0, width, height); - mSpaceKey.iconPreview = mSlidingLocaleIcon; - } - mSlidingLocaleIcon.setDiff(diff); - if (Math.abs(diff) == Integer.MAX_VALUE) { - mSpaceKey.iconPreview = mSpacePreviewIcon; - } else { - mSpaceKey.iconPreview = mSlidingLocaleIcon; - } - mSpaceKey.iconPreview.invalidateSelf(); - } - - public int getLanguageChangeDirection() { - if (mSpaceKey == null || SubtypeSwitcher.getInstance().getEnabledKeyboardLocaleCount() <= 1 - || Math.abs(mSpaceDragLastDiff) < mSpaceKey.width * SPACEBAR_DRAG_THRESHOLD) { - return 0; // No change - } - return mSpaceDragLastDiff > 0 ? 1 : -1; - } - - boolean isCurrentlyInSpace() { - return mCurrentlyInSpace; - } - - public void setPreferredLetters(int[] frequencies) { - mPrefLetterFrequencies = frequencies; - mPrefLetter = 0; - } - - public void keyReleased() { - mCurrentlyInSpace = false; - mSpaceDragLastDiff = 0; - mPrefLetter = 0; - mPrefLetterX = 0; - mPrefLetterY = 0; - mPrefDistance = Integer.MAX_VALUE; - if (mSpaceKey != null) { - updateLocaleDrag(Integer.MAX_VALUE); - } - } - - /** - * Does the magic of locking the touch gesture into the spacebar when - * switching input languages. - */ - public boolean isInside(LatinKey key, int x, int y) { - final int code = key.codes[0]; - if (code == KEYCODE_SHIFT || code == KEYCODE_DELETE) { - y -= key.height / 10; - if (code == KEYCODE_SHIFT) x += key.width / 6; - if (code == KEYCODE_DELETE) x -= key.width / 6; - } else if (code == LatinIME.KEYCODE_SPACE) { - y += LatinKeyboard.sSpacebarVerticalCorrection; - if (SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCHER - && SubtypeSwitcher.getInstance().getEnabledKeyboardLocaleCount() > 1) { - if (mCurrentlyInSpace) { - int diff = x - mSpaceDragStartX; - if (Math.abs(diff - mSpaceDragLastDiff) > 0) { - updateLocaleDrag(diff); - } - mSpaceDragLastDiff = diff; - return true; - } else { - boolean insideSpace = key.isInsideSuper(x, y); - if (insideSpace) { - mCurrentlyInSpace = true; - mSpaceDragStartX = x; - updateLocaleDrag(0); - } - return insideSpace; - } - } - } else if (mPrefLetterFrequencies != null) { - // New coordinate? Reset - if (mPrefLetterX != x || mPrefLetterY != y) { - mPrefLetter = 0; - mPrefDistance = Integer.MAX_VALUE; - } - // Handle preferred next letter - final int[] pref = mPrefLetterFrequencies; - if (mPrefLetter > 0) { - if (DEBUG_PREFERRED_LETTER) { - if (mPrefLetter == code && !key.isInsideSuper(x, y)) { - Log.d(TAG, "CORRECTED !!!!!!"); - } - } - return mPrefLetter == code; - } else { - final boolean inside = key.isInsideSuper(x, y); - int[] nearby = getNearestKeys(x, y); - List nearbyKeys = getKeys(); - if (inside) { - // If it's a preferred letter - if (inPrefList(code, pref)) { - // Check if its frequency is much lower than a nearby key - mPrefLetter = code; - mPrefLetterX = x; - mPrefLetterY = y; - for (int i = 0; i < nearby.length; i++) { - Key k = nearbyKeys.get(nearby[i]); - if (k != key && inPrefList(k.codes[0], pref)) { - final int dist = distanceFrom(k, x, y); - if (dist < (int) (k.width * OVERLAP_PERCENTAGE_LOW_PROB) && - (pref[k.codes[0]] > pref[mPrefLetter] * 3)) { - mPrefLetter = k.codes[0]; - mPrefDistance = dist; - if (DEBUG_PREFERRED_LETTER) { - Log.d(TAG, "CORRECTED ALTHOUGH PREFERRED !!!!!!"); - } - break; - } - } - } - - return mPrefLetter == code; - } - } - - // Get the surrounding keys and intersect with the preferred list - // For all in the intersection - // if distance from touch point is within a reasonable distance - // make this the pref letter - // If no pref letter - // return inside; - // else return thiskey == prefletter; - - for (int i = 0; i < nearby.length; i++) { - Key k = nearbyKeys.get(nearby[i]); - if (inPrefList(k.codes[0], pref)) { - final int dist = distanceFrom(k, x, y); - if (dist < (int) (k.width * OVERLAP_PERCENTAGE_HIGH_PROB) - && dist < mPrefDistance) { - mPrefLetter = k.codes[0]; - mPrefLetterX = x; - mPrefLetterY = y; - mPrefDistance = dist; - } - } - } - // Didn't find any - if (mPrefLetter == 0) { - return inside; - } else { - return mPrefLetter == code; - } - } - } - - // Lock into the spacebar - if (mCurrentlyInSpace) return false; - - return key.isInsideSuper(x, y); - } - - private boolean inPrefList(int code, int[] pref) { - if (code < pref.length && code >= 0) return pref[code] > 0; - return false; - } - - private int distanceFrom(Key k, int x, int y) { - if (y > k.y && y < k.y + k.height) { - return Math.abs(k.x + k.width / 2 - x); - } else { - return Integer.MAX_VALUE; - } - } - - @Override - public int[] getNearestKeys(int x, int y) { - if (mCurrentlyInSpace) { - return new int[] { mSpaceKeyIndex }; - } else { - // Avoid dead pixels at edges of the keyboard - return super.getNearestKeys(Math.max(0, Math.min(x, getMinWidth() - 1)), - Math.max(0, Math.min(y, getHeight() - 1))); - } - } - - private int indexOf(int code) { - List keys = getKeys(); - int count = keys.size(); - for (int i = 0; i < count; i++) { - if (keys.get(i).codes[0] == code) return i; - } - return -1; - } - - private int getTextSizeFromTheme(int style, int defValue) { - TypedArray array = mContext.getTheme().obtainStyledAttributes( - style, new int[] { android.R.attr.textSize }); - int textSize = array.getDimensionPixelSize(array.getResourceId(0, 0), defValue); - return textSize; - } - - public static class LatinKey extends BaseKeyboard.Key { - - // functional normal state (with properties) - private final int[] KEY_STATE_FUNCTIONAL_NORMAL = { - android.R.attr.state_single - }; - - // functional pressed state (with properties) - private final int[] KEY_STATE_FUNCTIONAL_PRESSED = { - android.R.attr.state_single, - android.R.attr.state_pressed - }; - - private boolean mShiftLockEnabled; - - public LatinKey(Resources res, BaseKeyboard.Row parent, int x, int y, - XmlResourceParser parser, KeyStyles keyStyles) { - super(res, parent, x, y, parser, keyStyles); - if (popupCharacters != null && popupCharacters.length() == 0) { - // If there is a keyboard with no keys specified in popupCharacters - popupResId = 0; - } - } - - private void enableShiftLock() { - mShiftLockEnabled = true; - } - - // sticky is used for shift key. If a key is not sticky and is modifier, - // the key will be treated as functional. - private boolean isFunctionalKey() { - return !sticky && modifier; - } - - @Override - public void onReleased(boolean inside) { - if (!mShiftLockEnabled) { - super.onReleased(inside); - } else { - pressed = !pressed; - } - } - - /** - * Overriding this method so that we can reduce the target area for certain keys. - */ - @Override - public boolean isInside(int x, int y) { - boolean result = (keyboard instanceof LatinKeyboard) - && ((LatinKeyboard)keyboard).isInside(this, x, y); - return result; - } - - private boolean isInsideSuper(int x, int y) { - return super.isInside(x, y); - } - - @Override - public int[] getCurrentDrawableState() { - if (isFunctionalKey()) { - if (pressed) { - return KEY_STATE_FUNCTIONAL_PRESSED; - } else { - return KEY_STATE_FUNCTIONAL_NORMAL; - } - } - return super.getCurrentDrawableState(); - } - } - - /** - * Animation to be displayed on the spacebar preview popup when switching - * languages by swiping the spacebar. It draws the current, previous and - * next languages and moves them by the delta of touch movement on the spacebar. - */ - private class SlidingLocaleDrawable extends Drawable { - - private final int mWidth; - private final int mHeight; - private final Drawable mBackground; - private final TextPaint mTextPaint; - private final int mMiddleX; - private final Drawable mLeftDrawable; - private final Drawable mRightDrawable; - private final int mThreshold; - private int mDiff; - private boolean mHitThreshold; - private String mCurrentLanguage; - private String mNextLanguage; - private String mPrevLanguage; - - public SlidingLocaleDrawable(Drawable background, int width, int height) { - mBackground = background; - setDefaultBounds(mBackground); - mWidth = width; - mHeight = height; - final TextPaint textPaint = new TextPaint(); - textPaint.setTextSize(getTextSizeFromTheme(android.R.style.TextAppearance_Medium, 18)); - textPaint.setColor(R.color.latinkeyboard_transparent); - textPaint.setTextAlign(Align.CENTER); - textPaint.setAlpha(OPACITY_FULLY_OPAQUE); - textPaint.setAntiAlias(true); - mTextPaint = textPaint; - mMiddleX = (mWidth - mBackground.getIntrinsicWidth()) / 2; - final Resources res = mRes; - mLeftDrawable = res.getDrawable( - R.drawable.sym_keyboard_feedback_language_arrows_left); - mRightDrawable = res.getDrawable( - R.drawable.sym_keyboard_feedback_language_arrows_right); - mThreshold = ViewConfiguration.get(mContext).getScaledTouchSlop(); - } - - private void setDiff(int diff) { - if (diff == Integer.MAX_VALUE) { - mHitThreshold = false; - mCurrentLanguage = null; - return; - } - mDiff = diff; - if (mDiff > mWidth) mDiff = mWidth; - if (mDiff < -mWidth) mDiff = -mWidth; - if (Math.abs(mDiff) > mThreshold) mHitThreshold = true; - invalidateSelf(); - } - - - @Override - public void draw(Canvas canvas) { - canvas.save(); - if (mHitThreshold) { - Paint paint = mTextPaint; - final int width = mWidth; - final int height = mHeight; - final int diff = mDiff; - final Drawable lArrow = mLeftDrawable; - final Drawable rArrow = mRightDrawable; - canvas.clipRect(0, 0, width, height); - if (mCurrentLanguage == null) { - SubtypeSwitcher subtypeSwitcher = SubtypeSwitcher.getInstance(); - mCurrentLanguage = subtypeSwitcher.getInputLanguageName(); - mNextLanguage = subtypeSwitcher.getNextInputLanguageName(); - mPrevLanguage = subtypeSwitcher.getPreviousInputLanguageName(); - } - // Draw language text with shadow - final float baseline = mHeight * SPACEBAR_LANGUAGE_BASELINE - paint.descent(); - paint.setColor(mRes.getColor(R.color.latinkeyboard_feedback_language_text)); - canvas.drawText(mCurrentLanguage, width / 2 + diff, baseline, paint); - canvas.drawText(mNextLanguage, diff - width / 2, baseline, paint); - canvas.drawText(mPrevLanguage, diff + width + width / 2, baseline, paint); - - setDefaultBounds(lArrow); - rArrow.setBounds(width - rArrow.getIntrinsicWidth(), 0, width, - rArrow.getIntrinsicHeight()); - lArrow.draw(canvas); - rArrow.draw(canvas); - } - if (mBackground != null) { - canvas.translate(mMiddleX, 0); - mBackground.draw(canvas); - } - canvas.restore(); - } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - @Override - public void setAlpha(int alpha) { - // Ignore - } - - @Override - public void setColorFilter(ColorFilter cf) { - // Ignore - } - - @Override - public int getIntrinsicWidth() { - return mWidth; - } - - @Override - public int getIntrinsicHeight() { - return mHeight; - } - } -} diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardShiftState.java b/java/src/com/android/inputmethod/latin/LatinKeyboardShiftState.java deleted file mode 100644 index e916306c8..000000000 --- a/java/src/com/android/inputmethod/latin/LatinKeyboardShiftState.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.android.inputmethod.latin; - -import android.util.Log; - -public class LatinKeyboardShiftState { - private static final String TAG = "LatinKeyboardShiftState"; - private static final boolean DEBUG = KeyboardSwitcher.DEBUG_STATE; - - private static final int NORMAL = 0; - private static final int MANUAL_SHIFTED = 1; - private static final int SHIFT_LOCKED = 2; - private static final int AUTO_SHIFTED = 3; - private static final int SHIFT_LOCK_SHIFTED = 4; - - private int mState = NORMAL; - - public boolean setShifted(boolean newShiftState) { - final int oldState = mState; - if (newShiftState) { - if (oldState == NORMAL || oldState == AUTO_SHIFTED) { - mState = MANUAL_SHIFTED; - } else if (oldState == SHIFT_LOCKED) { - mState = SHIFT_LOCK_SHIFTED; - } - } else { - if (oldState == MANUAL_SHIFTED || oldState == AUTO_SHIFTED) { - mState = NORMAL; - } else if (oldState == SHIFT_LOCK_SHIFTED) { - mState = SHIFT_LOCKED; - } - } - if (DEBUG) - Log.d(TAG, "setShifted(" + newShiftState + "): " + toString(oldState) + " > " + this); - return mState != oldState; - } - - public void setShiftLocked(boolean newShiftLockState) { - final int oldState = mState; - if (newShiftLockState) { - if (oldState == NORMAL || oldState == MANUAL_SHIFTED || oldState == AUTO_SHIFTED) - mState = SHIFT_LOCKED; - } else { - if (oldState == SHIFT_LOCKED || oldState == SHIFT_LOCK_SHIFTED) - mState = NORMAL; - } - if (DEBUG) - Log.d(TAG, "setShiftLocked(" + newShiftLockState + "): " + toString(oldState) - + " > " + this); - } - - public void setAutomaticTemporaryUpperCase() { - final int oldState = mState; - mState = AUTO_SHIFTED; - if (DEBUG) - Log.d(TAG, "setAutomaticTemporaryUpperCase: " + toString(oldState) + " > " + this); - } - - public boolean isShiftedOrShiftLocked() { - return mState != NORMAL; - } - - public boolean isShiftLocked() { - return mState == SHIFT_LOCKED || mState == SHIFT_LOCK_SHIFTED; - } - - public boolean isAutomaticTemporaryUpperCase() { - return mState == AUTO_SHIFTED; - } - - public boolean isManualTemporaryUpperCase() { - return mState == MANUAL_SHIFTED || mState == SHIFT_LOCK_SHIFTED; - } - - @Override - public String toString() { - return toString(mState); - } - - private static String toString(int state) { - switch (state) { - case NORMAL: return "NORMAL"; - case MANUAL_SHIFTED: return "MANUAL_SHIFTED"; - case SHIFT_LOCKED: return "SHIFT_LOCKED"; - case AUTO_SHIFTED: return "AUTO_SHIFTED"; - case SHIFT_LOCK_SHIFTED: return "SHIFT_LOCK_SHIFTED"; - default: return "UKNOWN"; - } - } -} \ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardView.java b/java/src/com/android/inputmethod/latin/LatinKeyboardView.java deleted file mode 100644 index ac68e3c39..000000000 --- a/java/src/com/android/inputmethod/latin/LatinKeyboardView.java +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Copyright (C) 2008 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.latin; - -import com.android.inputmethod.latin.BaseKeyboard.Key; -import com.android.inputmethod.voice.VoiceIMEConnector; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.MotionEvent; - -import java.util.List; - -public class LatinKeyboardView extends BaseKeyboardView { - - public static final int KEYCODE_OPTIONS = -100; - public static final int KEYCODE_OPTIONS_LONGPRESS = -101; - // TODO: remove this once LatinIME stops referring to this. - public static final int KEYCODE_VOICE = -102; - public static final int KEYCODE_NEXT_LANGUAGE = -104; - public static final int KEYCODE_PREV_LANGUAGE = -105; - public static final int KEYCODE_CAPSLOCK = -106; - - /** Whether we've started dropping move events because we found a big jump */ - private boolean mDroppingEvents; - /** - * Whether multi-touch disambiguation needs to be disabled if a real multi-touch event has - * occured - */ - private boolean mDisableDisambiguation; - /** The distance threshold at which we start treating the touch session as a multi-touch */ - private int mJumpThresholdSquare = Integer.MAX_VALUE; - /** The y coordinate of the last row */ - private int mLastRowY; - - public LatinKeyboardView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - public void setPreviewEnabled(boolean previewEnabled) { - LatinKeyboard latinKeyboard = getLatinKeyboard(); - if (latinKeyboard != null - && (latinKeyboard.isPhoneKeyboard() || latinKeyboard.isNumberKeyboard())) { - // Phone and number keyboard never shows popup preview (except language switch). - super.setPreviewEnabled(false); - } else { - super.setPreviewEnabled(previewEnabled); - } - } - - public void setLatinKeyboard(LatinKeyboard k) { - super.setKeyboard(k); - // One-seventh of the keyboard width seems like a reasonable threshold - mJumpThresholdSquare = k.getMinWidth() / 7; - mJumpThresholdSquare *= mJumpThresholdSquare; - // Assuming there are 4 rows, this is the coordinate of the last row - mLastRowY = (k.getHeight() * 3) / 4; - setKeyboardLocal(k); - } - - public LatinKeyboard getLatinKeyboard() { - BaseKeyboard keyboard = getKeyboard(); - if (keyboard instanceof LatinKeyboard) { - return (LatinKeyboard)keyboard; - } else { - return null; - } - } - - @Override - protected boolean onLongPress(Key key) { - int primaryCode = key.codes[0]; - if (primaryCode == KEYCODE_OPTIONS) { - return invokeOnKey(KEYCODE_OPTIONS_LONGPRESS); - } else if (primaryCode == '0' && getLatinKeyboard().isPhoneKeyboard()) { - // Long pressing on 0 in phone number keypad gives you a '+'. - return invokeOnKey('+'); - } else { - return super.onLongPress(key); - } - } - - private boolean invokeOnKey(int primaryCode) { - getOnKeyboardActionListener().onKey(primaryCode, null, - BaseKeyboardView.NOT_A_TOUCH_COORDINATE, - BaseKeyboardView.NOT_A_TOUCH_COORDINATE); - return true; - } - - @Override - protected CharSequence adjustCase(CharSequence label) { - LatinKeyboard keyboard = getLatinKeyboard(); - if (keyboard.isAlphaKeyboard() - && keyboard.isShiftedOrShiftLocked() - && !TextUtils.isEmpty(label) && label.length() < 3 - && Character.isLowerCase(label.charAt(0))) { - label = label.toString().toUpperCase(); - } - return label; - } - - /** - * This function checks to see if we need to handle any sudden jumps in the pointer location - * that could be due to a multi-touch being treated as a move by the firmware or hardware. - * Once a sudden jump is detected, all subsequent move events are discarded - * until an UP is received.

- * When a sudden jump is detected, an UP event is simulated at the last position and when - * the sudden moves subside, a DOWN event is simulated for the second key. - * @param me the motion event - * @return true if the event was consumed, so that it doesn't continue to be handled by - * KeyboardView. - */ - private boolean handleSuddenJump(MotionEvent me) { - final int action = me.getAction(); - final int x = (int) me.getX(); - final int y = (int) me.getY(); - boolean result = false; - - // Real multi-touch event? Stop looking for sudden jumps - if (me.getPointerCount() > 1) { - mDisableDisambiguation = true; - } - if (mDisableDisambiguation) { - // If UP, reset the multi-touch flag - if (action == MotionEvent.ACTION_UP) mDisableDisambiguation = false; - return false; - } - - switch (action) { - case MotionEvent.ACTION_DOWN: - // Reset the "session" - mDroppingEvents = false; - mDisableDisambiguation = false; - break; - case MotionEvent.ACTION_MOVE: - // Is this a big jump? - final int distanceSquare = (mLastX - x) * (mLastX - x) + (mLastY - y) * (mLastY - y); - // Check the distance and also if the move is not entirely within the bottom row - // If it's only in the bottom row, it might be an intentional slide gesture - // for language switching - if (distanceSquare > mJumpThresholdSquare - && (mLastY < mLastRowY || y < mLastRowY)) { - // If we're not yet dropping events, start dropping and send an UP event - if (!mDroppingEvents) { - mDroppingEvents = true; - // Send an up event - MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(), - MotionEvent.ACTION_UP, - mLastX, mLastY, me.getMetaState()); - super.onTouchEvent(translated); - translated.recycle(); - } - result = true; - } else if (mDroppingEvents) { - // If moves are small and we're already dropping events, continue dropping - result = true; - } - break; - case MotionEvent.ACTION_UP: - if (mDroppingEvents) { - // Send a down event first, as we dropped a bunch of sudden jumps and assume that - // the user is releasing the touch on the second key. - MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(), - MotionEvent.ACTION_DOWN, - x, y, me.getMetaState()); - super.onTouchEvent(translated); - translated.recycle(); - mDroppingEvents = false; - // Let the up event get processed as well, result = false - } - break; - } - // Track the previous coordinate - mLastX = x; - mLastY = y; - return result; - } - - @Override - public boolean onTouchEvent(MotionEvent me) { - LatinKeyboard keyboard = getLatinKeyboard(); - if (DEBUG_LINE) { - mLastX = (int) me.getX(); - mLastY = (int) me.getY(); - invalidate(); - } - - // If there was a sudden jump, return without processing the actual motion event. - if (handleSuddenJump(me)) - return true; - - // Reset any bounding box controls in the keyboard - if (me.getAction() == MotionEvent.ACTION_DOWN) { - keyboard.keyReleased(); - } - - if (me.getAction() == MotionEvent.ACTION_UP) { - int languageDirection = keyboard.getLanguageChangeDirection(); - if (languageDirection != 0) { - getOnKeyboardActionListener().onKey( - languageDirection == 1 ? KEYCODE_NEXT_LANGUAGE : KEYCODE_PREV_LANGUAGE, - null, mLastX, mLastY); - me.setAction(MotionEvent.ACTION_CANCEL); - keyboard.keyReleased(); - return super.onTouchEvent(me); - } - } - - return super.onTouchEvent(me); - } - - /**************************** INSTRUMENTATION *******************************/ - - static final boolean DEBUG_AUTO_PLAY = false; - static final boolean DEBUG_LINE = false; - private static final int MSG_TOUCH_DOWN = 1; - private static final int MSG_TOUCH_UP = 2; - - Handler mHandler2; - - private String mStringToPlay; - private int mStringIndex; - private boolean mDownDelivered; - private Key[] mAsciiKeys = new Key[256]; - private boolean mPlaying; - private int mLastX; - private int mLastY; - private Paint mPaint; - - private void setKeyboardLocal(LatinKeyboard k) { - if (DEBUG_AUTO_PLAY) { - findKeys(); - if (mHandler2 == null) { - mHandler2 = new Handler() { - @Override - public void handleMessage(Message msg) { - removeMessages(MSG_TOUCH_DOWN); - removeMessages(MSG_TOUCH_UP); - if (mPlaying == false) return; - - switch (msg.what) { - case MSG_TOUCH_DOWN: - if (mStringIndex >= mStringToPlay.length()) { - mPlaying = false; - return; - } - char c = mStringToPlay.charAt(mStringIndex); - while (c > 255 || mAsciiKeys[c] == null) { - mStringIndex++; - if (mStringIndex >= mStringToPlay.length()) { - mPlaying = false; - return; - } - c = mStringToPlay.charAt(mStringIndex); - } - int x = mAsciiKeys[c].x + 10; - int y = mAsciiKeys[c].y + 26; - MotionEvent me = MotionEvent.obtain(SystemClock.uptimeMillis(), - SystemClock.uptimeMillis(), - MotionEvent.ACTION_DOWN, x, y, 0); - LatinKeyboardView.this.dispatchTouchEvent(me); - me.recycle(); - sendEmptyMessageDelayed(MSG_TOUCH_UP, 500); // Deliver up in 500ms if nothing else - // happens - mDownDelivered = true; - break; - case MSG_TOUCH_UP: - char cUp = mStringToPlay.charAt(mStringIndex); - int x2 = mAsciiKeys[cUp].x + 10; - int y2 = mAsciiKeys[cUp].y + 26; - mStringIndex++; - - MotionEvent me2 = MotionEvent.obtain(SystemClock.uptimeMillis(), - SystemClock.uptimeMillis(), - MotionEvent.ACTION_UP, x2, y2, 0); - LatinKeyboardView.this.dispatchTouchEvent(me2); - me2.recycle(); - sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 500); // Deliver up in 500ms if nothing else - // happens - mDownDelivered = false; - break; - } - } - }; - - } - } - } - - private void findKeys() { - List keys = getLatinKeyboard().getKeys(); - // Get the keys on this keyboard - for (int i = 0; i < keys.size(); i++) { - int code = keys.get(i).codes[0]; - if (code >= 0 && code <= 255) { - mAsciiKeys[code] = keys.get(i); - } - } - } - - public void startPlaying(String s) { - if (DEBUG_AUTO_PLAY) { - if (s == null) return; - mStringToPlay = s.toLowerCase(); - mPlaying = true; - mDownDelivered = false; - mStringIndex = 0; - mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 10); - } - } - - @Override - public void draw(Canvas c) { - LatinIMEUtil.GCUtils.getInstance().reset(); - boolean tryGC = true; - for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { - try { - super.draw(c); - tryGC = false; - } catch (OutOfMemoryError e) { - tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait("LatinKeyboardView", e); - } - } - if (DEBUG_AUTO_PLAY) { - if (mPlaying) { - mHandler2.removeMessages(MSG_TOUCH_DOWN); - mHandler2.removeMessages(MSG_TOUCH_UP); - if (mDownDelivered) { - mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_UP, 20); - } else { - mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 20); - } - } - } - if (DEBUG_LINE) { - if (mPaint == null) { - mPaint = new Paint(); - mPaint.setColor(0x80FFFFFF); - mPaint.setAntiAlias(false); - } - c.drawLine(mLastX, 0, mLastX, getHeight(), mPaint); - c.drawLine(0, mLastY, getWidth(), mLastY, mPaint); - } - } - - @Override - protected void onAttachedToWindow() { - // Token is available from here. - VoiceIMEConnector.getInstance().onAttachedToWindow(); - } -} diff --git a/java/src/com/android/inputmethod/latin/MiniKeyboardKeyDetector.java b/java/src/com/android/inputmethod/latin/MiniKeyboardKeyDetector.java deleted file mode 100644 index 3cc43b99c..000000000 --- a/java/src/com/android/inputmethod/latin/MiniKeyboardKeyDetector.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * 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.latin; - -import com.android.inputmethod.latin.BaseKeyboard.Key; - -class MiniKeyboardKeyDetector extends KeyDetector { - private static final int MAX_NEARBY_KEYS = 1; - - private final int mSlideAllowanceSquare; - private final int mSlideAllowanceSquareTop; - - public MiniKeyboardKeyDetector(float slideAllowance) { - super(); - mSlideAllowanceSquare = (int)(slideAllowance * slideAllowance); - // Top slide allowance is slightly longer (sqrt(2) times) than other edges. - mSlideAllowanceSquareTop = mSlideAllowanceSquare * 2; - } - - @Override - protected int getMaxNearbyKeys() { - return MAX_NEARBY_KEYS; - } - - @Override - public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) { - final Key[] keys = getKeys(); - final int touchX = getTouchX(x); - final int touchY = getTouchY(y); - - int closestKeyIndex = BaseKeyboardView.NOT_A_KEY; - int closestKeyDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare; - final int keyCount = keys.length; - for (int index = 0; index < keyCount; index++) { - final int dist = keys[index].squaredDistanceToEdge(touchX, touchY); - if (dist < closestKeyDist) { - closestKeyIndex = index; - closestKeyDist = dist; - } - } - - if (allKeys != null && closestKeyIndex != BaseKeyboardView.NOT_A_KEY) - allKeys[0] = keys[closestKeyIndex].codes[0]; - return closestKeyIndex; - } -} diff --git a/java/src/com/android/inputmethod/latin/PointerTracker.java b/java/src/com/android/inputmethod/latin/PointerTracker.java deleted file mode 100644 index 327fef107..000000000 --- a/java/src/com/android/inputmethod/latin/PointerTracker.java +++ /dev/null @@ -1,546 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * 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.latin; - -import com.android.inputmethod.latin.BaseKeyboard.Key; -import com.android.inputmethod.latin.BaseKeyboardView.OnKeyboardActionListener; -import com.android.inputmethod.latin.BaseKeyboardView.UIHandler; - -import android.content.res.Resources; -import android.util.Log; -import android.view.MotionEvent; - -public class PointerTracker { - private static final String TAG = "PointerTracker"; - private static final boolean DEBUG = false; - private static final boolean DEBUG_MOVE = false; - - public interface UIProxy { - public void invalidateKey(Key key); - public void showPreview(int keyIndex, PointerTracker tracker); - public boolean hasDistinctMultitouch(); - } - - public final int mPointerId; - - // Timing constants - private final int mDelayBeforeKeyRepeatStart; - private final int mLongPressKeyTimeout; - private final int mLongPressShiftKeyTimeout; - private final int mMultiTapKeyTimeout; - - // Miscellaneous constants - private static final int NOT_A_KEY = BaseKeyboardView.NOT_A_KEY; - private static final int[] KEY_DELETE = { BaseKeyboard.KEYCODE_DELETE }; - - private final UIProxy mProxy; - private final UIHandler mHandler; - private final KeyDetector mKeyDetector; - private OnKeyboardActionListener mListener; - private final boolean mHasDistinctMultitouch; - - private BaseKeyboard mKeyboard; - private Key[] mKeys; - private int mKeyHysteresisDistanceSquared = -1; - - private final KeyState mKeyState; - - // true if event is already translated to a key action (long press or mini-keyboard) - private boolean mKeyAlreadyProcessed; - - // true if this pointer is repeatable key - private boolean mIsRepeatableKey; - - // For multi-tap - private int mLastSentIndex; - private int mTapCount; - private long mLastTapTime; - private boolean mInMultiTap; - private final StringBuilder mPreviewLabel = new StringBuilder(1); - - // pressed key - private int mPreviousKey = NOT_A_KEY; - - // This class keeps track of a key index and a position where this pointer is. - private static class KeyState { - private final KeyDetector mKeyDetector; - - // The position and time at which first down event occurred. - private int mStartX; - private int mStartY; - private long mDownTime; - - // The current key index where this pointer is. - private int mKeyIndex = NOT_A_KEY; - // The position where mKeyIndex was recognized for the first time. - private int mKeyX; - private int mKeyY; - - // Last pointer position. - private int mLastX; - private int mLastY; - - public KeyState(KeyDetector keyDetecor) { - mKeyDetector = keyDetecor; - } - - public int getKeyIndex() { - return mKeyIndex; - } - - public int getKeyX() { - return mKeyX; - } - - public int getKeyY() { - return mKeyY; - } - - public int getStartX() { - return mStartX; - } - - public int getStartY() { - return mStartY; - } - - public long getDownTime() { - return mDownTime; - } - - public int getLastX() { - return mLastX; - } - - public int getLastY() { - return mLastY; - } - - public int onDownKey(int x, int y, long eventTime) { - mStartX = x; - mStartY = y; - mDownTime = eventTime; - - return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); - } - - private int onMoveKeyInternal(int x, int y) { - mLastX = x; - mLastY = y; - return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); - } - - public int onMoveKey(int x, int y) { - return onMoveKeyInternal(x, y); - } - - public int onMoveToNewKey(int keyIndex, int x, int y) { - mKeyIndex = keyIndex; - mKeyX = x; - mKeyY = y; - return keyIndex; - } - - public int onUpKey(int x, int y) { - return onMoveKeyInternal(x, y); - } - - public void onSetKeyboard() { - mKeyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(mKeyX, mKeyY, null); - } - } - - public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector, UIProxy proxy, - Resources res) { - if (proxy == null || handler == null || keyDetector == null) - throw new NullPointerException(); - mPointerId = id; - mProxy = proxy; - mHandler = handler; - mKeyDetector = keyDetector; - mKeyState = new KeyState(keyDetector); - mHasDistinctMultitouch = proxy.hasDistinctMultitouch(); - mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start); - mLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout); - mLongPressShiftKeyTimeout = res.getInteger(R.integer.config_long_press_shift_key_timeout); - mMultiTapKeyTimeout = res.getInteger(R.integer.config_multi_tap_key_timeout); - resetMultiTap(); - } - - public void setOnKeyboardActionListener(OnKeyboardActionListener listener) { - mListener = listener; - } - - public void setKeyboard(BaseKeyboard keyboard, Key[] keys, float keyHysteresisDistance) { - if (keyboard == null || keys == null || keyHysteresisDistance < 0) - throw new IllegalArgumentException(); - mKeyboard = keyboard; - mKeys = keys; - mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance); - // Update current key index because keyboard layout has been changed. - mKeyState.onSetKeyboard(); - } - - private boolean isValidKeyIndex(int keyIndex) { - return keyIndex >= 0 && keyIndex < mKeys.length; - } - - public Key getKey(int keyIndex) { - return isValidKeyIndex(keyIndex) ? mKeys[keyIndex] : null; - } - - private boolean isModifierInternal(int keyIndex) { - Key key = getKey(keyIndex); - if (key == null) - return false; - int primaryCode = key.codes[0]; - return primaryCode == BaseKeyboard.KEYCODE_SHIFT - || primaryCode == BaseKeyboard.KEYCODE_MODE_CHANGE; - } - - public boolean isModifier() { - return isModifierInternal(mKeyState.getKeyIndex()); - } - - public boolean isOnModifierKey(int x, int y) { - return isModifierInternal(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null)); - } - - public boolean isSpaceKey(int keyIndex) { - Key key = getKey(keyIndex); - return key != null && key.codes[0] == LatinIME.KEYCODE_SPACE; - } - - public void releaseKey() { - updateKeyGraphics(NOT_A_KEY); - } - - private void updateKeyGraphics(int keyIndex) { - int oldKeyIndex = mPreviousKey; - mPreviousKey = keyIndex; - if (keyIndex != oldKeyIndex) { - if (isValidKeyIndex(oldKeyIndex)) { - // if new key index is not a key, old key was just released inside of the key. - final boolean inside = (keyIndex == NOT_A_KEY); - mKeys[oldKeyIndex].onReleased(inside); - mProxy.invalidateKey(mKeys[oldKeyIndex]); - } - if (isValidKeyIndex(keyIndex)) { - mKeys[keyIndex].onPressed(); - mProxy.invalidateKey(mKeys[keyIndex]); - } - } - } - - public void setAlreadyProcessed() { - mKeyAlreadyProcessed = true; - } - - public void onTouchEvent(int action, int x, int y, long eventTime) { - switch (action) { - case MotionEvent.ACTION_MOVE: - onMoveEvent(x, y, eventTime); - break; - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - onDownEvent(x, y, eventTime); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - onUpEvent(x, y, eventTime); - break; - case MotionEvent.ACTION_CANCEL: - onCancelEvent(x, y, eventTime); - break; - } - } - - public void onDownEvent(int x, int y, long eventTime) { - if (DEBUG) - debugLog("onDownEvent:", x, y); - int keyIndex = mKeyState.onDownKey(x, y, eventTime); - mKeyAlreadyProcessed = false; - mIsRepeatableKey = false; - checkMultiTap(eventTime, keyIndex); - if (mListener != null) { - if (isValidKeyIndex(keyIndex)) { - mListener.onPress(mKeys[keyIndex].codes[0]); - // This onPress call may have changed keyboard layout and have updated mKeyIndex. - // If that's the case, mKeyIndex has been updated in setKeyboard(). - keyIndex = mKeyState.getKeyIndex(); - } - } - if (isValidKeyIndex(keyIndex)) { - if (mKeys[keyIndex].repeatable) { - repeatKey(keyIndex); - mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this); - mIsRepeatableKey = true; - } - startLongPressTimer(keyIndex); - } - showKeyPreviewAndUpdateKeyGraphics(keyIndex); - } - - public void onMoveEvent(int x, int y, long eventTime) { - if (DEBUG_MOVE) - debugLog("onMoveEvent:", x, y); - if (mKeyAlreadyProcessed) - return; - KeyState keyState = mKeyState; - final int keyIndex = keyState.onMoveKey(x, y); - final Key oldKey = getKey(keyState.getKeyIndex()); - if (isValidKeyIndex(keyIndex)) { - if (oldKey == null) { - keyState.onMoveToNewKey(keyIndex, x, y); - startLongPressTimer(keyIndex); - } else if (!isMinorMoveBounce(x, y, keyIndex)) { - if (mListener != null) - mListener.onRelease(oldKey.codes[0]); - resetMultiTap(); - keyState.onMoveToNewKey(keyIndex, x, y); - startLongPressTimer(keyIndex); - } - } else { - if (oldKey != null) { - if (mListener != null) - mListener.onRelease(oldKey.codes[0]); - keyState.onMoveToNewKey(keyIndex, x ,y); - mHandler.cancelLongPressTimers(); - } else if (!isMinorMoveBounce(x, y, keyIndex)) { - resetMultiTap(); - keyState.onMoveToNewKey(keyIndex, x ,y); - mHandler.cancelLongPressTimers(); - } - } - showKeyPreviewAndUpdateKeyGraphics(mKeyState.getKeyIndex()); - } - - public void onUpEvent(int x, int y, long eventTime) { - if (DEBUG) - debugLog("onUpEvent :", x, y); - showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY); - if (mKeyAlreadyProcessed) - return; - mHandler.cancelKeyTimers(); - mHandler.cancelPopupPreview(); - int keyIndex = mKeyState.onUpKey(x, y); - if (isMinorMoveBounce(x, y, keyIndex)) { - // Use previous fixed key index and coordinates. - keyIndex = mKeyState.getKeyIndex(); - x = mKeyState.getKeyX(); - y = mKeyState.getKeyY(); - } - if (!mIsRepeatableKey) { - detectAndSendKey(keyIndex, x, y, eventTime); - } - - if (isValidKeyIndex(keyIndex)) - mProxy.invalidateKey(mKeys[keyIndex]); - } - - public void onCancelEvent(int x, int y, long eventTime) { - if (DEBUG) - debugLog("onCancelEvt:", x, y); - mHandler.cancelKeyTimers(); - mHandler.cancelPopupPreview(); - showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY); - int keyIndex = mKeyState.getKeyIndex(); - if (isValidKeyIndex(keyIndex)) - mProxy.invalidateKey(mKeys[keyIndex]); - } - - public void repeatKey(int keyIndex) { - Key key = getKey(keyIndex); - if (key != null) { - // While key is repeating, because there is no need to handle multi-tap key, we can - // pass -1 as eventTime argument. - detectAndSendKey(keyIndex, key.x, key.y, -1); - } - } - - public int getLastX() { - return mKeyState.getLastX(); - } - - public int getLastY() { - return mKeyState.getLastY(); - } - - public long getDownTime() { - return mKeyState.getDownTime(); - } - - // These package scope methods are only for debugging purpose. - /* package */ int getStartX() { - return mKeyState.getStartX(); - } - - /* package */ int getStartY() { - return mKeyState.getStartY(); - } - - private boolean isMinorMoveBounce(int x, int y, int newKey) { - if (mKeys == null || mKeyHysteresisDistanceSquared < 0) - throw new IllegalStateException("keyboard and/or hysteresis not set"); - int curKey = mKeyState.getKeyIndex(); - if (newKey == curKey) { - return true; - } else if (isValidKeyIndex(curKey)) { - return mKeys[curKey].squaredDistanceToEdge(x, y) < mKeyHysteresisDistanceSquared; - } else { - return false; - } - } - - private void showKeyPreviewAndUpdateKeyGraphics(int keyIndex) { - updateKeyGraphics(keyIndex); - // The modifier key, such as shift key, should not be shown as preview when multi-touch is - // supported. On the other hand, if multi-touch is not supported, the modifier key should - // be shown as preview. - if (mHasDistinctMultitouch && isModifier()) { - mProxy.showPreview(NOT_A_KEY, this); - } else { - mProxy.showPreview(keyIndex, this); - } - } - - private void startLongPressTimer(int keyIndex) { - Key key = getKey(keyIndex); - if (key.codes[0] == BaseKeyboard.KEYCODE_SHIFT) { - mHandler.startLongPressShiftTimer(mLongPressShiftKeyTimeout, keyIndex, this); - } else { - mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this); - } - } - - private boolean isManualTemporaryUpperCase() { - return mKeyboard instanceof LatinKeyboard - && ((LatinKeyboard)mKeyboard).isManualTemporaryUpperCase(); - } - - private void detectAndSendKey(int index, int x, int y, long eventTime) { - final OnKeyboardActionListener listener = mListener; - final Key key = getKey(index); - - if (key == null) { - if (listener != null) - listener.onCancel(); - } else { - if (key.text != null) { - if (listener != null) { - listener.onText(key.text); - listener.onRelease(NOT_A_KEY); - } - } else { - int code = key.codes[0]; - //TextEntryState.keyPressedAt(key, x, y); - int[] codes = mKeyDetector.newCodeArray(); - mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes); - // Multi-tap - if (mInMultiTap) { - if (mTapCount != -1) { - mListener.onKey(BaseKeyboard.KEYCODE_DELETE, KEY_DELETE, x, y); - } else { - mTapCount = 0; - } - code = key.codes[mTapCount]; - } - - // If keyboard is in manual temporary upper case state and key has manual temporary - // shift code, alternate character code should be sent. - if (isManualTemporaryUpperCase() && key.manualTemporaryUpperCaseCode != 0) { - code = key.manualTemporaryUpperCaseCode; - codes[0] = code; - } - - /* - * Swap the first and second values in the codes array if the primary code is not - * the first value but the second value in the array. This happens when key - * debouncing is in effect. - */ - if (codes.length >= 2 && codes[0] != code && codes[1] == code) { - codes[1] = codes[0]; - codes[0] = code; - } - if (listener != null) { - listener.onKey(code, codes, x, y); - listener.onRelease(code); - } - } - mLastSentIndex = index; - mLastTapTime = eventTime; - } - } - - /** - * Handle multi-tap keys by producing the key label for the current multi-tap state. - */ - public CharSequence getPreviewText(Key key) { - if (mInMultiTap) { - // Multi-tap - mPreviewLabel.setLength(0); - mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]); - return mPreviewLabel; - } else { - return key.label; - } - } - - private void resetMultiTap() { - mLastSentIndex = NOT_A_KEY; - mTapCount = 0; - mLastTapTime = -1; - mInMultiTap = false; - } - - private void checkMultiTap(long eventTime, int keyIndex) { - Key key = getKey(keyIndex); - if (key == null) - return; - - final boolean isMultiTap = - (eventTime < mLastTapTime + mMultiTapKeyTimeout && keyIndex == mLastSentIndex); - if (key.codes.length > 1) { - mInMultiTap = true; - if (isMultiTap) { - mTapCount = (mTapCount + 1) % key.codes.length; - return; - } else { - mTapCount = -1; - return; - } - } - if (!isMultiTap) { - resetMultiTap(); - } - } - - private void debugLog(String title, int x, int y) { - int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); - Key key = getKey(keyIndex); - final String code; - if (key == null) { - code = "----"; - } else { - int primaryCode = key.codes[0]; - code = String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode); - } - Log.d(TAG, String.format("%s%s[%d] %3d,%3d %3d(%s) %s", title, - (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, keyIndex, code, - (isModifier() ? "modifier" : ""))); - } -} diff --git a/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java b/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java deleted file mode 100644 index 35bdc6728..000000000 --- a/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * 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.latin; - -import com.android.inputmethod.latin.BaseKeyboard.Key; - -import java.util.Arrays; - -class ProximityKeyDetector extends KeyDetector { - private static final int MAX_NEARBY_KEYS = 12; - - // working area - private int[] mDistances = new int[MAX_NEARBY_KEYS]; - - @Override - protected int getMaxNearbyKeys() { - return MAX_NEARBY_KEYS; - } - - @Override - public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) { - final Key[] keys = getKeys(); - final int touchX = getTouchX(x); - final int touchY = getTouchY(y); - - int primaryIndex = BaseKeyboardView.NOT_A_KEY; - int closestKeyIndex = BaseKeyboardView.NOT_A_KEY; - int closestKeyDist = mProximityThresholdSquare + 1; - final int[] distances = mDistances; - Arrays.fill(distances, Integer.MAX_VALUE); - for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) { - final Key key = keys[index]; - final boolean isInside = key.isInside(touchX, touchY); - if (isInside) - primaryIndex = index; - final int dist = key.squaredDistanceToEdge(touchX, touchY); - if (isInside || (mProximityCorrectOn && dist < mProximityThresholdSquare)) { - if (dist < closestKeyDist) { - closestKeyDist = dist; - closestKeyIndex = index; - } - - if (allKeys == null) continue; - final int nCodes = key.codes.length; - // Find insertion point - for (int j = 0; j < distances.length; j++) { - if (distances[j] > dist) { - // Make space for nCodes codes - System.arraycopy(distances, j, distances, j + nCodes, - distances.length - (j + nCodes)); - System.arraycopy(allKeys, j, allKeys, j + nCodes, - allKeys.length - (j + nCodes)); - System.arraycopy(key.codes, 0, allKeys, j, nCodes); - Arrays.fill(distances, j, j + nCodes, dist); - break; - } - } - } - } - - return primaryIndex == BaseKeyboardView.NOT_A_KEY ? closestKeyIndex : primaryIndex; - } -} diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index 103443e6d..a98917689 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.keyboard.LatinKeyboard; import com.android.inputmethod.voice.SettingsUtil; import com.android.inputmethod.voice.VoiceIMEConnector; import com.android.inputmethod.voice.VoiceInput; @@ -197,7 +198,7 @@ public class SubtypeSwitcher { || VoiceIMEConnector.getInstance().needsToShowWarningDialog()) { if (mVoiceInput != null) { // TODO: Call proper function to trigger VoiceIME - mService.onKey(LatinKeyboardView.KEYCODE_VOICE, null, 0, 0); + mService.onKey(LatinKeyboard.KEYCODE_VOICE, null, 0, 0); } } } else { @@ -350,7 +351,7 @@ public class SubtypeSwitcher { if (DBG) { Log.d(TAG, "Set and call voice input."); } - mService.onKey(LatinKeyboardView.KEYCODE_VOICE, null, 0, 0); + mService.onKey(LatinKeyboard.KEYCODE_VOICE, null, 0, 0); return true; } } diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index ca2ffe13a..4c9b7509a 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -423,6 +423,7 @@ public class Suggest implements Dictionary.WordCallback { return false; } + @Override public boolean addWord(final char[] word, final int offset, final int length, int freq, final int dicTypeId, final Dictionary.DataType dataType) { Dictionary.DataType dataTypeForLog = dataType; diff --git a/java/src/com/android/inputmethod/latin/SwipeTracker.java b/java/src/com/android/inputmethod/latin/SwipeTracker.java deleted file mode 100644 index 970e91965..000000000 --- a/java/src/com/android/inputmethod/latin/SwipeTracker.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * 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.latin; - -import android.view.MotionEvent; - -class SwipeTracker { - private static final int NUM_PAST = 4; - private static final int LONGEST_PAST_TIME = 200; - - final EventRingBuffer mBuffer = new EventRingBuffer(NUM_PAST); - - private float mYVelocity; - private float mXVelocity; - - public void addMovement(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mBuffer.clear(); - return; - } - long time = ev.getEventTime(); - final int count = ev.getHistorySize(); - for (int i = 0; i < count; i++) { - addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i), ev.getHistoricalEventTime(i)); - } - addPoint(ev.getX(), ev.getY(), time); - } - - private void addPoint(float x, float y, long time) { - final EventRingBuffer buffer = mBuffer; - while (buffer.size() > 0) { - long lastT = buffer.getTime(0); - if (lastT >= time - LONGEST_PAST_TIME) - break; - buffer.dropOldest(); - } - buffer.add(x, y, time); - } - - public void computeCurrentVelocity(int units) { - computeCurrentVelocity(units, Float.MAX_VALUE); - } - - public void computeCurrentVelocity(int units, float maxVelocity) { - final EventRingBuffer buffer = mBuffer; - final float oldestX = buffer.getX(0); - final float oldestY = buffer.getY(0); - final long oldestTime = buffer.getTime(0); - - float accumX = 0; - float accumY = 0; - final int count = buffer.size(); - for (int pos = 1; pos < count; pos++) { - final int dur = (int)(buffer.getTime(pos) - oldestTime); - if (dur == 0) continue; - float dist = buffer.getX(pos) - oldestX; - float vel = (dist / dur) * units; // pixels/frame. - if (accumX == 0) accumX = vel; - else accumX = (accumX + vel) * .5f; - - dist = buffer.getY(pos) - oldestY; - vel = (dist / dur) * units; // pixels/frame. - if (accumY == 0) accumY = vel; - else accumY = (accumY + vel) * .5f; - } - mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) - : Math.min(accumX, maxVelocity); - mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) - : Math.min(accumY, maxVelocity); - } - - public float getXVelocity() { - return mXVelocity; - } - - public float getYVelocity() { - return mYVelocity; - } - - static class EventRingBuffer { - private final int bufSize; - private final float xBuf[]; - private final float yBuf[]; - private final long timeBuf[]; - private int top; // points new event - private int end; // points oldest event - private int count; // the number of valid data - - public EventRingBuffer(int max) { - this.bufSize = max; - xBuf = new float[max]; - yBuf = new float[max]; - timeBuf = new long[max]; - clear(); - } - - public void clear() { - top = end = count = 0; - } - - public int size() { - return count; - } - - // Position 0 points oldest event - private int index(int pos) { - return (end + pos) % bufSize; - } - - private int advance(int index) { - return (index + 1) % bufSize; - } - - public void add(float x, float y, long time) { - xBuf[top] = x; - yBuf[top] = y; - timeBuf[top] = time; - top = advance(top); - if (count < bufSize) { - count++; - } else { - end = advance(end); - } - } - - public float getX(int pos) { - return xBuf[index(pos)]; - } - - public float getY(int pos) { - return yBuf[index(pos)]; - } - - public long getTime(int pos) { - return timeBuf[index(pos)]; - } - - public void dropOldest() { - count--; - end = advance(end); - } - } -} \ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java index 56347af5d..31a192bfe 100644 --- a/java/src/com/android/inputmethod/latin/TextEntryState.java +++ b/java/src/com/android/inputmethod/latin/TextEntryState.java @@ -16,7 +16,7 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.latin.BaseKeyboard.Key; +import com.android.inputmethod.keyboard.Key; import android.content.Context; import android.text.format.DateFormat; diff --git a/java/src/com/android/inputmethod/latin/Tutorial.java b/java/src/com/android/inputmethod/latin/Tutorial.java index cd7636f3c..bd069bd0d 100644 --- a/java/src/com/android/inputmethod/latin/Tutorial.java +++ b/java/src/com/android/inputmethod/latin/Tutorial.java @@ -16,6 +16,8 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.keyboard.LatinKeyboardView; + import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Handler; @@ -133,6 +135,7 @@ public class Tutorial implements OnTouchListener { if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) offy -= window.getHeight(); if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) offx -= window.getWidth(); textView.setOnTouchListener(new View.OnTouchListener() { + @Override public boolean onTouch(View view, MotionEvent me) { Tutorial.this.next(); return true; @@ -237,6 +240,7 @@ public class Tutorial implements OnTouchListener { return true; } + @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { next(); diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java index 49b95e9aa..a522303e6 100644 --- a/java/src/com/android/inputmethod/latin/UserDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserDictionary.java @@ -97,6 +97,7 @@ public class UserDictionary extends ExpandableDictionary { final ContentResolver contentResolver = getContext().getContentResolver(); new Thread("addWord") { + @Override public void run() { contentResolver.insert(Words.CONTENT_URI, values); } diff --git a/java/src/com/android/inputmethod/voice/FieldContext.java b/java/src/com/android/inputmethod/voice/FieldContext.java index 5fbacfb6c..dfdfbaa9f 100644 --- a/java/src/com/android/inputmethod/voice/FieldContext.java +++ b/java/src/com/android/inputmethod/voice/FieldContext.java @@ -73,6 +73,7 @@ public class FieldContext { bundle.putInt(IME_OPTIONS, info.imeOptions); } + @SuppressWarnings("static-access") private static void addInputConnectionToBundle( InputConnection conn, Bundle bundle) { if (conn == null) { @@ -96,6 +97,7 @@ public class FieldContext { return mFieldInfo; } + @Override public String toString() { return mFieldInfo.toString(); } diff --git a/java/src/com/android/inputmethod/voice/RecognitionView.java b/java/src/com/android/inputmethod/voice/RecognitionView.java index 1d1297713..12d0de852 100644 --- a/java/src/com/android/inputmethod/voice/RecognitionView.java +++ b/java/src/com/android/inputmethod/voice/RecognitionView.java @@ -51,6 +51,7 @@ import java.util.List; * plays beeps, shows errors, etc. */ public class RecognitionView { + @SuppressWarnings("unused") private static final String TAG = "RecognitionView"; private Handler mUiHandler; // Reference to UI thread @@ -78,6 +79,7 @@ public class RecognitionView { /** Updates the microphone icon to show user their volume.*/ private Runnable mUpdateVolumeRunnable = new Runnable() { + @Override public void run() { if (mState != State.LISTENING) { return; @@ -141,6 +143,7 @@ public class RecognitionView { public void restoreState() { mUiHandler.post(new Runnable() { + @Override public void run() { // Restart the spinner if (mState == State.WORKING) { @@ -153,6 +156,7 @@ public class RecognitionView { public void showInitializing() { mUiHandler.post(new Runnable() { + @Override public void run() { prepareDialog(false, mContext.getText(R.string.voice_initializing), mInitializing, mContext.getText(R.string.cancel)); @@ -162,6 +166,7 @@ public class RecognitionView { public void showListening() { mUiHandler.post(new Runnable() { + @Override public void run() { mState = State.LISTENING; prepareDialog(false, mContext.getText(R.string.voice_listening), mSpeakNow.get(0), @@ -177,6 +182,7 @@ public class RecognitionView { public void showError(final String message) { mUiHandler.post(new Runnable() { + @Override public void run() { mState = State.READY; prepareDialog(false, message, mError, mContext.getText(R.string.ok)); @@ -190,6 +196,7 @@ public class RecognitionView { final int speechEndPosition) { mUiHandler.post(new Runnable() { + @Override public void run() { mState = State.WORKING; prepareDialog(true, mContext.getText(R.string.voice_working), null, mContext @@ -309,6 +316,7 @@ public class RecognitionView { public void finish() { mUiHandler.post(new Runnable() { + @Override public void run() { mState = State.READY; exitWorking(); diff --git a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java index 5574a21de..73b3b19e3 100644 --- a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java +++ b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java @@ -39,7 +39,6 @@ import android.text.Selection; import android.text.Spannable; import android.text.TextUtils; import android.text.method.LinkMovementMethod; -import android.text.method.MovementMethod; import android.text.style.ClickableSpan; import android.text.style.URLSpan; import android.view.LayoutInflater; @@ -120,6 +119,7 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { if (VOICE_INSTALLED) { mVoiceInput = new VoiceInput(context, this); mHints = new Hints(context, prefs, new Hints.Display() { + @Override public void showHint(int viewResource) { LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( Context.LAYOUT_INFLATER_SERVICE); @@ -519,6 +519,7 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { public void switchToRecognitionStatusView(final boolean configurationChanging) { final boolean configChanged = configurationChanging; mHandler.post(new Runnable() { + @Override public void run() { mContext.setCandidatesViewShown(false); mRecognizing = true; diff --git a/java/src/com/android/inputmethod/voice/VoiceInput.java b/java/src/com/android/inputmethod/voice/VoiceInput.java index 6d45ef97c..d51d8694d 100644 --- a/java/src/com/android/inputmethod/voice/VoiceInput.java +++ b/java/src/com/android/inputmethod/voice/VoiceInput.java @@ -86,6 +86,7 @@ public class VoiceInput implements OnClickListener { private static final String ALTERNATES_BUNDLE = "alternates_bundle"; // This is copied from the VoiceSearch app. + @SuppressWarnings("unused") private static final class AlternatesBundleKeys { public static final String ALTERNATES = "alternates"; public static final String CONFIDENCE = "confidence"; @@ -405,6 +406,7 @@ public class VoiceInput implements OnClickListener { /** * Handle the cancel button. */ + @Override public void onClick(View view) { switch(view.getId()) { case R.id.button: @@ -556,36 +558,43 @@ public class VoiceInput implements OnClickListener { int mSpeechStart; private boolean mEndpointed = false; + @Override public void onReadyForSpeech(Bundle noiseParams) { mRecognitionView.showListening(); } + @Override public void onBeginningOfSpeech() { mEndpointed = false; mSpeechStart = mWaveBuffer.size(); } + @Override public void onRmsChanged(float rmsdB) { mRecognitionView.updateVoiceMeter(rmsdB); } + @Override public void onBufferReceived(byte[] buf) { try { mWaveBuffer.write(buf); } catch (IOException e) {} } + @Override public void onEndOfSpeech() { mEndpointed = true; mState = WORKING; mRecognitionView.showWorking(mWaveBuffer, mSpeechStart, mWaveBuffer.size()); } + @Override public void onError(int errorType) { mState = ERROR; VoiceInput.this.onError(errorType, mEndpointed); } + @Override public void onResults(Bundle resultsBundle) { List results = resultsBundle .getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION); @@ -638,10 +647,12 @@ public class VoiceInput implements OnClickListener { mRecognitionView.finish(); } + @Override public void onPartialResults(final Bundle partialResults) { // currently - do nothing } + @Override public void onEvent(int eventType, Bundle params) { // do nothing - reserved for events that might be added in the future } diff --git a/java/src/com/android/inputmethod/voice/VoiceInputLogger.java b/java/src/com/android/inputmethod/voice/VoiceInputLogger.java index ec0ae649a..3e65434a2 100644 --- a/java/src/com/android/inputmethod/voice/VoiceInputLogger.java +++ b/java/src/com/android/inputmethod/voice/VoiceInputLogger.java @@ -31,6 +31,7 @@ import android.content.Intent; * on on the VoiceSearch side. */ public class VoiceInputLogger { + @SuppressWarnings("unused") private static final String TAG = VoiceInputLogger.class.getSimpleName(); private static VoiceInputLogger sVoiceInputLogger; -- cgit v1.2.3-83-g751a