aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/keyboard/Key.java364
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyDetector.java (renamed from java/src/com/android/inputmethod/latin/KeyDetector.java)11
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyStyles.java246
-rw-r--r--java/src/com/android/inputmethod/keyboard/Keyboard.java454
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java99
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardId.java156
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardParser.java571
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardShiftState.java105
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java646
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java (renamed from java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java)812
-rw-r--r--java/src/com/android/inputmethod/keyboard/LatinKeyboard.java431
-rw-r--r--java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java (renamed from java/src/com/android/inputmethod/latin/LatinKeyboardView.java)105
-rw-r--r--java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java (renamed from java/src/com/android/inputmethod/latin/MiniKeyboardKeyDetector.java)21
-rw-r--r--java/src/com/android/inputmethod/keyboard/ModifierKeyState.java79
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java (renamed from java/src/com/android/inputmethod/latin/PointerTracker.java)131
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java79
-rw-r--r--java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java (renamed from java/src/com/android/inputmethod/latin/ProximityKeyDetector.java)53
-rw-r--r--java/src/com/android/inputmethod/keyboard/Row.java81
-rw-r--r--java/src/com/android/inputmethod/keyboard/ShiftKeyState.java69
-rw-r--r--java/src/com/android/inputmethod/keyboard/SlidingLocaleDrawable.java163
-rw-r--r--java/src/com/android/inputmethod/keyboard/SwipeTracker.java (renamed from java/src/com/android/inputmethod/latin/SwipeTracker.java)8
-rw-r--r--java/src/com/android/inputmethod/latin/AutoDictionary.java8
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java54
-rw-r--r--[-rwxr-xr-x]java/src/com/android/inputmethod/latin/CandidateView.java0
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java4
-rw-r--r--java/src/com/android/inputmethod/latin/InputLanguageSelection.java33
-rw-r--r--java/src/com/android/inputmethod/latin/KeyboardSwitcher.java538
-rw-r--r--java/src/com/android/inputmethod/latin/LanguageSwitcher.java68
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java1569
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIMEDebugSettings.java1
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIMESettings.java87
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIMEUtil.java242
-rw-r--r--java/src/com/android/inputmethod/latin/LatinImeLogger.java10
-rw-r--r--java/src/com/android/inputmethod/latin/LatinKeyboard.java1022
-rw-r--r--java/src/com/android/inputmethod/latin/ModifierKeyState.java42
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java478
-rw-r--r--[-rwxr-xr-x]java/src/com/android/inputmethod/latin/Suggest.java48
-rw-r--r--java/src/com/android/inputmethod/latin/TextEntryState.java26
-rw-r--r--java/src/com/android/inputmethod/latin/Tutorial.java117
-rw-r--r--java/src/com/android/inputmethod/latin/UserBigramDictionary.java8
-rw-r--r--java/src/com/android/inputmethod/latin/UserDictionary.java1
-rw-r--r--java/src/com/android/inputmethod/voice/FieldContext.java2
-rw-r--r--java/src/com/android/inputmethod/voice/Hints.java (renamed from java/src/com/android/inputmethod/latin/Hints.java)37
-rw-r--r--java/src/com/android/inputmethod/voice/RecognitionView.java22
-rw-r--r--java/src/com/android/inputmethod/voice/SettingsUtil.java3
-rw-r--r--java/src/com/android/inputmethod/voice/VoiceIMEConnector.java688
-rw-r--r--java/src/com/android/inputmethod/voice/VoiceInput.java29
-rw-r--r--java/src/com/android/inputmethod/voice/VoiceInputLogger.java17
-rw-r--r--java/src/com/android/inputmethod/voice/Whitelist.java1
49 files changed, 6460 insertions, 3379 deletions
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..87d128fe0
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -0,0 +1,364 @@
+/*
+ * 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 final int[] mCodes;
+ /** The unicode that this key generates in manual temporary upper case mode. */
+ public final int mManualTemporaryUpperCaseCode;
+
+ /** Label to display */
+ public final CharSequence mLabel;
+ /** Option of the label */
+ public final int mLabelOption;
+
+ /** Icon to display instead of a label. Icon takes precedence over a label */
+ private Drawable mIcon;
+ /** Preview version of the icon, for the preview popup */
+ private Drawable mPreviewIcon;
+ /** Hint icon to display on the key in conjunction with the label */
+ public final Drawable mHintIcon;
+ /**
+ * The hint icon to display on the key when keyboard is in manual temporary upper case
+ * mode.
+ */
+ public final Drawable mManualTemporaryUpperCaseHintIcon;
+
+ /** Width of the key, not including the gap */
+ public final int mWidth;
+ /** Height of the key, not including the gap */
+ public final int mHeight;
+ /** The horizontal gap before this key */
+ public final int mGap;
+ /** Whether this key is sticky, i.e., a toggle key */
+ public final boolean mSticky;
+ /** X coordinate of the key in the keyboard layout */
+ public final int mX;
+ /** Y coordinate of the key in the keyboard layout */
+ public final int mY;
+ /** Text to output when pressed. This can be multiple characters, like ".com" */
+ public final CharSequence mOutputText;
+ /** Popup characters */
+ public final CharSequence mPopupCharacters;
+ /**
+ * If this key pops up a mini keyboard, this is the resource id for the XML layout for that
+ * keyboard.
+ */
+ public final int mPopupResId;
+
+ /**
+ * 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 final int mEdgeFlags;
+ /** Whether this is a modifier key, such as Shift or Alt */
+ public final boolean mModifier;
+ /** Whether this key repeats itself when held down */
+ public final boolean mRepeatable;
+
+ /** The Keyboard that this key belongs to */
+ private final Keyboard mKeyboard;
+
+ /** The current pressed state of this key */
+ public boolean mPressed;
+ /** If this is a sticky key, is it on? */
+ public boolean mOn;
+
+ 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
+ };
+
+ // functional normal state (with properties)
+ private static final int[] KEY_STATE_FUNCTIONAL_NORMAL = {
+ android.R.attr.state_single
+ };
+
+ // functional pressed state (with properties)
+ private static final int[] KEY_STATE_FUNCTIONAL_PRESSED = {
+ android.R.attr.state_single,
+ android.R.attr.state_pressed
+ };
+
+ /** Create an empty key with no attributes. */
+ public Key(Row row, char letter, int x, int y) {
+ mKeyboard = row.getKeyboard();
+ mHeight = row.mDefaultHeight;
+ mGap = row.mDefaultHorizontalGap;
+ mWidth = row.mDefaultWidth - mGap;
+ mEdgeFlags = row.mRowEdgeFlags;
+ mHintIcon = null;
+ mManualTemporaryUpperCaseHintIcon = null;
+ mManualTemporaryUpperCaseCode = 0;
+ mLabelOption = 0;
+ mModifier = false;
+ mSticky = false;
+ mRepeatable = false;
+ mOutputText = null;
+ mPopupCharacters = null;
+ mPopupResId = 0;
+ mLabel = String.valueOf(letter);
+ mCodes = new int[] { letter };
+ // Horizontal gap is divided equally to both sides of the key.
+ mX = x + mGap / 2;
+ mY = y;
+ }
+
+ /** 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 row 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 row, int x, int y, XmlResourceParser parser,
+ KeyStyles keyStyles) {
+ mKeyboard = row.getKeyboard();
+
+ TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard);
+ mHeight = KeyboardParser.getDimensionOrFraction(a,
+ R.styleable.Keyboard_keyHeight,
+ mKeyboard.getKeyboardHeight(), row.mDefaultHeight);
+ mGap = KeyboardParser.getDimensionOrFraction(a,
+ R.styleable.Keyboard_horizontalGap,
+ mKeyboard.getKeyboardWidth(), row.mDefaultHorizontalGap);
+ mWidth = KeyboardParser.getDimensionOrFraction(a,
+ R.styleable.Keyboard_keyWidth,
+ mKeyboard.getKeyboardWidth(), row.mDefaultWidth) - mGap;
+ 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.mX = x + mGap / 2;
+ this.mY = y;
+
+ int[] codes = style.getIntArray(a, R.styleable.Keyboard_Key_codes);
+ mPreviewIcon = style.getDrawable(a, R.styleable.Keyboard_Key_iconPreview);
+ Keyboard.setDefaultBounds(mPreviewIcon);
+ mPopupCharacters = style.getText(a, R.styleable.Keyboard_Key_popupCharacters);
+ mPopupResId = style.getResourceId(a, R.styleable.Keyboard_Key_popupKeyboard, 0);
+ mRepeatable = style.getBoolean(a, R.styleable.Keyboard_Key_isRepeatable, false);
+ mModifier = style.getBoolean(a, R.styleable.Keyboard_Key_isModifier, false);
+ mSticky = style.getBoolean(a, R.styleable.Keyboard_Key_isSticky, false);
+ mEdgeFlags = style.getFlag(a, R.styleable.Keyboard_Key_keyEdgeFlags, 0)
+ | row.mRowEdgeFlags;
+
+ mIcon = style.getDrawable(a, R.styleable.Keyboard_Key_keyIcon);
+ Keyboard.setDefaultBounds(mIcon);
+ mHintIcon = style.getDrawable(a, R.styleable.Keyboard_Key_keyHintIcon);
+ Keyboard.setDefaultBounds(mHintIcon);
+ mManualTemporaryUpperCaseHintIcon = style.getDrawable(a,
+ R.styleable.Keyboard_Key_manualTemporaryUpperCaseHintIcon);
+ Keyboard.setDefaultBounds(mManualTemporaryUpperCaseHintIcon);
+
+ mLabel = style.getText(a, R.styleable.Keyboard_Key_keyLabel);
+ mLabelOption = style.getFlag(a, R.styleable.Keyboard_Key_keyLabelOption, 0);
+ mManualTemporaryUpperCaseCode = style.getInt(a,
+ R.styleable.Keyboard_Key_manualTemporaryUpperCaseCode, 0);
+ mOutputText = style.getText(a, R.styleable.Keyboard_Key_keyOutputText);
+ final Drawable shiftedIcon = style.getDrawable(a,
+ R.styleable.Keyboard_Key_shiftedIcon);
+ a.recycle();
+
+ if (shiftedIcon != null)
+ mKeyboard.getShiftedIcons().put(this, shiftedIcon);
+
+ if (codes == null && !TextUtils.isEmpty(mLabel))
+ codes = new int[] { mLabel.charAt(0) };
+ mCodes = codes;
+ }
+
+ public Drawable getIcon() {
+ return mIcon;
+ }
+
+ public Drawable getPreviewIcon() {
+ return mPreviewIcon;
+ }
+
+ public void setIcon(Drawable icon) {
+ mIcon = icon;
+ }
+
+ public void setPreviewIcon(Drawable icon) {
+ mPreviewIcon = icon;
+ }
+
+ /**
+ * Informs the key that it has been pressed, in case it needs to change its appearance or
+ * state.
+ * @see #onReleased(boolean)
+ */
+ public void onPressed() {
+ mPressed = !mPressed;
+ }
+
+ /**
+ * 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) {
+ mPressed = !mPressed;
+ if (mSticky && !mKeyboard.isShiftLockEnabled(this))
+ mOn = !mOn;
+ }
+
+ public boolean isInside(int x, int y) {
+ return mKeyboard.isInside(this, x, y);
+ }
+
+ /**
+ * Detects if a point falls on 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 on 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 on the key.
+ */
+ public boolean isOnKey(int x, int y) {
+ final int flags = mEdgeFlags;
+ final boolean leftEdge = (flags & Keyboard.EDGE_LEFT) != 0;
+ final boolean rightEdge = (flags & Keyboard.EDGE_RIGHT) != 0;
+ final boolean topEdge = (flags & Keyboard.EDGE_TOP) != 0;
+ final boolean bottomEdge = (flags & Keyboard.EDGE_BOTTOM) != 0;
+ final int left = this.mX;
+ final int right = left + this.mWidth;
+ final int top = this.mY;
+ final int bottom = top + this.mHeight;
+ return (x >= left || leftEdge) && (x < right || rightEdge)
+ && (y >= top || topEdge) && (y < bottom || bottomEdge);
+ }
+
+ /**
+ * 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.mX;
+ final int right = left + this.mWidth;
+ final int top = this.mY;
+ final int bottom = top + this.mHeight;
+ 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;
+ }
+
+ // 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 !mSticky && mModifier;
+ }
+
+ /**
+ * 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() {
+ if (isFunctionalKey()) {
+ if (mPressed) {
+ return KEY_STATE_FUNCTIONAL_PRESSED;
+ } else {
+ return KEY_STATE_FUNCTIONAL_NORMAL;
+ }
+ }
+
+ int[] states = KEY_STATE_NORMAL;
+
+ if (mOn) {
+ if (mPressed) {
+ states = KEY_STATE_PRESSED_ON;
+ } else {
+ states = KEY_STATE_NORMAL_ON;
+ }
+ } else {
+ if (mSticky) {
+ if (mPressed) {
+ states = KEY_STATE_PRESSED_OFF;
+ } else {
+ states = KEY_STATE_NORMAL_OFF;
+ }
+ } else {
+ if (mPressed) {
+ states = KEY_STATE_PRESSED;
+ }
+ }
+ }
+ return states;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 76fe1200e..777a79520 100644
--- a/java/src/com/android/inputmethod/latin/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -14,15 +14,14 @@
* the License.
*/
-package com.android.inputmethod.latin;
-
-import android.inputmethodservice.Keyboard;
-import android.inputmethodservice.Keyboard.Key;
+package com.android.inputmethod.keyboard;
import java.util.Arrays;
import java.util.List;
-abstract class KeyDetector {
+public abstract class KeyDetector {
+ public static final int NOT_A_KEY = -1;
+
protected Keyboard mKeyboard;
private Key[] mKeys;
@@ -85,7 +84,7 @@ abstract class KeyDetector {
*/
public int[] newCodeArray() {
int[] codes = new int[getMaxNearbyKeys()];
- Arrays.fill(codes, LatinKeyboardBaseView.NOT_A_KEY);
+ Arrays.fill(codes, NOT_A_KEY);
return codes;
}
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<String, DeclaredKeyStyle> mStyles =
+ new HashMap<String, DeclaredKeyStyle>();
+ 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<Integer, Object> mAttributes = new HashMap<Integer, Object>();
+
+ @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..6a1d62efe
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -0,0 +1,454 @@
+/*
+ * 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.HashSet;
+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.
+ * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p>
+ * <pre>
+ * &lt;Keyboard
+ * latin:keyWidth="%10p"
+ * latin:keyHeight="50px"
+ * latin:horizontalGap="2px"
+ * latin:verticalGap="2px" &gt;
+ * &lt;Row latin:keyWidth="32px" &gt;
+ * &lt;Key latin:keyLabel="A" /&gt;
+ * ...
+ * &lt;/Row&gt;
+ * ...
+ * &lt;/Keyboard&gt;
+ * </pre>
+ */
+public class Keyboard {
+ private 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;
+
+ /** Some common keys code. These should be aligned with values/keycodes.xml */
+ public static final int CODE_ENTER = '\n';
+ public static final int CODE_TAB = '\t';
+ public static final int CODE_SPACE = ' ';
+ public static final int CODE_PERIOD = '.';
+
+ /** Special keys code. These should be aligned with values/keycodes.xml */
+ public static final int CODE_SHIFT = -1;
+ public static final int CODE_SWITCH_ALPHA_SYMBOL = -2;
+ public static final int CODE_CANCEL = -3;
+ public static final int CODE_DONE = -4;
+ public static final int CODE_DELETE = -5;
+ public static final int CODE_ALT = -6;
+ public static final int CODE_SETTINGS = -100;
+ public static final int CODE_SETTINGS_LONGPRESS = -101;
+ // TODO: remove this once LatinIME stops referring to this.
+ public static final int CODE_VOICE = -102;
+ public static final int CODE_CAPSLOCK = -103;
+ public static final int CODE_NEXT_LANGUAGE = -104;
+ public static final int CODE_PREV_LANGUAGE = -105;
+
+ /** 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;
+
+ /** List of shift keys in this keyboard and its icons and state */
+ private final List<Key> mShiftKeys = new ArrayList<Key>();
+ private final HashMap<Key, Drawable> mShiftedIcons = new HashMap<Key, Drawable>();
+ private final HashMap<Key, Drawable> mNormalShiftIcons = new HashMap<Key, Drawable>();
+ private final HashSet<Key> mShiftLockEnabled = new HashSet<Key>();
+ private final KeyboardShiftState mShiftState = new KeyboardShiftState();
+
+ /** Space key and its icons */
+ protected Key mSpaceKey;
+ protected Drawable mSpaceIcon;
+ protected Drawable mSpacePreviewIcon;
+
+ /** 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<Key> mKeys = new ArrayList<Key>();
+
+ /** Width of the screen available to fit the keyboard */
+ private final int mDisplayWidth;
+
+ /** Height of the screen */
+ private final int mDisplayHeight;
+
+ public 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);
+ }
+
+ /**
+ * <p>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.
+ * </p>
+ * <p>If the specified number of columns is -1, then the keyboard will fit as many keys as
+ * possible in each row.</p>
+ * @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;
+
+ final Row row = new Row(this);
+ 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, c, x, y);
+ column++;
+ x += key.mWidth + key.mGap;
+ mKeys.add(key);
+ if (x > mTotalWidth) {
+ mTotalWidth = x;
+ }
+ }
+ mTotalHeight = y + mDefaultHeight;
+ }
+
+ public List<Key> getKeys() {
+ return mKeys;
+ }
+
+ public int getHorizontalGap() {
+ return mDefaultHorizontalGap;
+ }
+
+ public void setHorizontalGap(int gap) {
+ mDefaultHorizontalGap = gap;
+ }
+
+ public int getVerticalGap() {
+ return mDefaultVerticalGap;
+ }
+
+ public void setVerticalGap(int gap) {
+ mDefaultVerticalGap = gap;
+ }
+
+ public int getKeyHeight() {
+ return mDefaultHeight;
+ }
+
+ public void setKeyHeight(int height) {
+ mDefaultHeight = height;
+ }
+
+ public int getKeyWidth() {
+ return mDefaultWidth;
+ }
+
+ public 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 List<Key> getShiftKeys() {
+ return mShiftKeys;
+ }
+
+ public Map<Key, Drawable> getShiftedIcons() {
+ return mShiftedIcons;
+ }
+
+ public void enableShiftLock() {
+ for (final Key key : getShiftKeys()) {
+ mShiftLockEnabled.add(key);
+ mNormalShiftIcons.put(key, key.getIcon());
+ }
+ }
+
+ public boolean isShiftLockEnabled(Key key) {
+ return mShiftLockEnabled.contains(key);
+ }
+
+ public boolean setShiftLocked(boolean newShiftLockState) {
+ final Map<Key, Drawable> shiftedIcons = getShiftedIcons();
+ for (final Key key : getShiftKeys()) {
+ key.mOn = newShiftLockState;
+ key.setIcon(newShiftLockState ? shiftedIcons.get(key) : mNormalShiftIcons.get(key));
+ }
+ mShiftState.setShiftLocked(newShiftLockState);
+ return true;
+ }
+
+ public boolean isShiftLocked() {
+ return mShiftState.isShiftLocked();
+ }
+
+ public boolean setShifted(boolean newShiftState) {
+ final Map<Key, Drawable> shiftedIcons = getShiftedIcons();
+ for (final Key key : getShiftKeys()) {
+ if (!newShiftState && !mShiftState.isShiftLocked()) {
+ key.setIcon(mNormalShiftIcons.get(key));
+ } else if (newShiftState && !mShiftState.isShiftedOrShiftLocked()) {
+ key.setIcon(shiftedIcons.get(key));
+ }
+ }
+ return mShiftState.setShifted(newShiftState);
+ }
+
+ public boolean isShiftedOrShiftLocked() {
+ return mShiftState.isShiftedOrShiftLocked();
+ }
+
+ public void setAutomaticTemporaryUpperCase() {
+ setShifted(true);
+ mShiftState.setAutomaticTemporaryUpperCase();
+ }
+
+ public boolean isAutomaticTemporaryUpperCase() {
+ return isAlphaKeyboard() && mShiftState.isAutomaticTemporaryUpperCase();
+ }
+
+ public boolean isManualTemporaryUpperCase() {
+ return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCase();
+ }
+
+ public KeyboardShiftState getKeyboardShiftState() {
+ return mShiftState;
+ }
+
+ public boolean isAlphaKeyboard() {
+ return mId != null && mId.isAlphabetKeyboard();
+ }
+
+ public boolean isPhoneKeyboard() {
+ return mId != null && mId.isPhoneKeyboard();
+ }
+
+ public boolean isNumberKeyboard() {
+ return mId != null && mId.isNumberKeyboard();
+ }
+
+ public void setSpaceKey(Key space) {
+ mSpaceKey = space;
+ mSpaceIcon = space.getIcon();
+ mSpacePreviewIcon = space.getPreviewIcon();
+ }
+
+ 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;
+ }
+ }
+ }
+
+ public boolean isInside(Key key, int x, int y) {
+ return key.isOnKey(x, y);
+ }
+
+ /**
+ * 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..883d2175c
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -0,0 +1,156 @@
+/*
+ * 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 isAlphabetKeyboard() {
+ return mXmlId == R.xml.kbd_qwerty;
+ }
+
+ public boolean isPhoneKeyboard() {
+ return mMode == MODE_PHONE;
+ }
+
+ public boolean isNumberKeyboard() {
+ return mMode == MODE_NUMBER;
+ }
+
+ @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..dd80f0ed0
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
@@ -0,0 +1,571 @@
+/*
+ * 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:
+ * <pre>
+ * &gt;!-- xml/keyboard.xml --&lt;
+ * &gt;Keyboard keyboard_attributes*&lt;
+ * &gt;!-- Keyboard Content --&lt;
+ * &gt;Row row_attributes*&lt;
+ * &gt;!-- Row Content --&lt;
+ * &gt;Key key_attributes* /&lt;
+ * &gt;Spacer horizontalGap="0.2in" /&lt;
+ * &gt;include keyboardLayout="@xml/other_keys"&lt;
+ * ...
+ * &gt;/Row&lt;
+ * &gt;include keyboardLayout="@xml/other_rows"&lt;
+ * ...
+ * &gt;/Keyboard&lt;
+ * </pre>
+ * The XML file which is included in other file must have &gt;merge&lt; as root element, such as:
+ * <pre>
+ * &gt;!-- xml/other_keys.xml --&lt;
+ * &gt;merge&lt;
+ * &gt;Key key_attributes* /&lt;
+ * ...
+ * &gt;/merge&lt;
+ * </pre>
+ * and
+ * <pre>
+ * &gt;!-- xml/other_rows.xml --&lt;
+ * &gt;merge&lt;
+ * &gt;Row row_attributes*&lt;
+ * &gt;Key key_attributes* /&lt;
+ * &gt;/Row&lt;
+ * ...
+ * &gt;/merge&lt;
+ * </pre>
+ * You can also use switch-case-default tags to select Rows and Keys.
+ * <pre>
+ * &gt;switch&lt;
+ * &gt;case case_attribute*&lt;
+ * &gt;!-- Any valid tags at switch position --&lt;
+ * &gt;/case&lt;
+ * ...
+ * &gt;default&lt;
+ * &gt;!-- Any valid tags at switch position --&lt;
+ * &gt;/default&lt;
+ * &gt;/switch&lt;
+ * </pre>
+ * You can declare Key style and specify styles within Key tags.
+ * <pre>
+ * &gt;switch&lt;
+ * &gt;case colorScheme="white"&lt;
+ * &gt;key-style styleName="shift-key" parentStyle="modifier-key"
+ * keyIcon="@drawable/sym_keyboard_shift"
+ * /&lt;
+ * &gt;/case&lt;
+ * &gt;case colorScheme="black"&lt;
+ * &gt;key-style styleName="shift-key" parentStyle="modifier-key"
+ * keyIcon="@drawable/sym_bkeyboard_shift"
+ * /&lt;
+ * &gt;/case&lt;
+ * &gt;/switch&lt;
+ * ...
+ * &gt;Key keyStyle="shift-key" ... /&lt;
+ * </pre>
+ */
+
+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<Key> 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<Key> 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<Key> 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.mCodes[0] == Keyboard.CODE_SHIFT)
+ mKeyboard.getShiftKeys().add(key);
+ if (key.mCodes[0] == Keyboard.CODE_SPACE)
+ mKeyboard.setSpaceKey(key);
+ endKey(key);
+ }
+ }
+
+ private void parseSpacer(XmlResourceParser parser, List<Key> 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<Key> keys)
+ throws XmlPullParserException, IOException {
+ parseIncludeInternal(parser, null, keys);
+ }
+
+ private void parseIncludeRowContent(XmlResourceParser parser, Row row, List<Key> keys)
+ throws XmlPullParserException, IOException {
+ parseIncludeInternal(parser, row, keys);
+ }
+
+ private void parseIncludeInternal(XmlResourceParser parser, Row row, List<Key> 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 <include/>", 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<Key> 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 <merge> root element", parser);
+ }
+ }
+ }
+ }
+
+ private void parseSwitchKeyboardContent(XmlResourceParser parser, List<Key> keys)
+ throws XmlPullParserException, IOException {
+ parseSwitchInternal(parser, null, keys);
+ }
+
+ private void parseSwitchRowContent(XmlResourceParser parser, Row row, List<Key> keys)
+ throws XmlPullParserException, IOException {
+ parseSwitchInternal(parser, row, keys);
+ }
+
+ private void parseSwitchInternal(XmlResourceParser parser, Row row, List<Key> 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<Key> 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 <case> does not have "index" attribute, that means this <case> 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 <case> does not have "index" attribute, that means this <case> is wild-card for the
+ // attribute.
+ return !a.hasValue(index) || a.getBoolean(index, false) == value;
+ }
+
+ private boolean parseDefault(XmlResourceParser parser, Row row, List<Key> keys)
+ throws XmlPullParserException, IOException {
+ if (row == null) {
+ parseKeyboardContent(parser, keys);
+ } else {
+ parseRowContent(parser, row, keys);
+ }
+ return true;
+ }
+
+ private void parseKeyStyle(XmlResourceParser parser, List<Key> 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.mVerticalGap + mCurrentRow.mDefaultHeight;
+ mCurrentRow = null;
+ }
+
+ private void endKey(Key key) {
+ mCurrentX += key.mGap + key.mWidth;
+ 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 + ": </" + tag + ">" + (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/KeyboardShiftState.java b/java/src/com/android/inputmethod/keyboard/KeyboardShiftState.java
new file mode 100644
index 000000000..3e1eaf44e
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardShiftState.java
@@ -0,0 +1,105 @@
+/*
+ * 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.util.Log;
+
+public class KeyboardShiftState {
+ private static final String TAG = "KeyboardShiftState";
+ 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/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
new file mode 100644
index 000000000..d3302fd6f
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -0,0 +1,646 @@
+/*
+ * 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.LatinIMESettings;
+import com.android.inputmethod.latin.LatinIMEUtil;
+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.SharedPreferences;
+import android.content.res.Resources;
+import android.util.Log;
+import android.view.InflateException;
+import android.view.inputmethod.InputMethodManager;
+
+import java.lang.ref.SoftReference;
+import java.util.HashMap;
+import java.util.Locale;
+
+public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceChangeListener {
+ private static final String TAG = "KeyboardSwitcher";
+ private static final boolean DEBUG = false;
+ public static final boolean DEBUG_STATE = false;
+
+ // 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";
+ private static final int[] THEMES = new int [] {
+ R.layout.input_basic,
+ R.layout.input_basic_highcontrast,
+ R.layout.input_stone_normal,
+ R.layout.input_stone_bold,
+ R.layout.input_gingerbread,
+ R.layout.input_honeycomb, // DEFAULT_LAYOUT_ID
+ };
+
+ private static final int SYMBOLS_MODE_STATE_NONE = 0;
+ private static final int SYMBOLS_MODE_STATE_BEGIN = 1;
+ private static final int SYMBOLS_MODE_STATE_SYMBOL = 2;
+
+ private SubtypeSwitcher mSubtypeSwitcher;
+ private SharedPreferences mPrefs;
+
+ private LatinKeyboardView mInputView;
+ private LatinIME mInputMethodService;
+
+ private ShiftKeyState mShiftKeyState = new ShiftKeyState("Shift");
+ private ModifierKeyState mSymbolKeyState = new ModifierKeyState("Symbol");
+
+ private KeyboardId mSymbolsId;
+ private KeyboardId mSymbolsShiftedId;
+
+ private KeyboardId mCurrentId;
+ private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboardCache =
+ new HashMap<KeyboardId, SoftReference<LatinKeyboard>>();
+
+ 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
+ * what user actually typed. */
+ private boolean mIsAutoCompletionActive;
+ private boolean mVoiceKeyEnabled;
+ private boolean mVoiceButtonOnPrimary;
+ private int mSymbolsModeState = SYMBOLS_MODE_STATE_NONE;
+
+ // Indicates whether or not we have the settings key
+ private boolean mHasSettingsKey;
+ private static final int SETTINGS_KEY_MODE_AUTO = R.string.settings_key_mode_auto;
+ private static final int SETTINGS_KEY_MODE_ALWAYS_SHOW =
+ R.string.settings_key_mode_always_show;
+ // NOTE: No need to have SETTINGS_KEY_MODE_ALWAYS_HIDE here because it's not being referred to
+ // in the source code now.
+ // Default is SETTINGS_KEY_MODE_AUTO.
+ private static final int DEFAULT_SETTINGS_KEY_MODE = SETTINGS_KEY_MODE_AUTO;
+
+ private int mLayoutId;
+
+ private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
+
+ public static KeyboardSwitcher getInstance() {
+ return sInstance;
+ }
+
+ private KeyboardSwitcher() {
+ }
+
+ public static void init(LatinIME ims, SharedPreferences prefs) {
+ sInstance.mInputMethodService = ims;
+ sInstance.mPrefs = prefs;
+ sInstance.mSubtypeSwitcher = SubtypeSwitcher.getInstance();
+
+ sInstance.mLayoutId = Integer.valueOf(
+ prefs.getString(PREF_KEYBOARD_LAYOUT, DEFAULT_LAYOUT_ID));
+ prefs.registerOnSharedPreferenceChangeListener(sInstance);
+ }
+
+ private void makeSymbolsKeyboardIds() {
+ final Locale locale = mSubtypeSwitcher.getInputLocale();
+ final int orientation = mInputMethodService.getResources().getConfiguration().orientation;
+ final int mode = mMode;
+ final int colorScheme = getColorScheme();
+ final boolean hasSettingsKey = mHasSettingsKey;
+ final boolean voiceKeyEnabled = mVoiceKeyEnabled;
+ final boolean hasVoiceKey = voiceKeyEnabled && !mVoiceButtonOnPrimary;
+ final int imeOptions = mImeOptions;
+ // Note: This comment is only applied for phone number keyboard layout.
+ // On non-xlarge device, "@integer/key_switch_alpha_symbol" key code is used to switch
+ // between "phone keyboard" and "phone symbols keyboard". But on xlarge device,
+ // "@integer/key_shift" key code is used for that purpose in order to properly display
+ // "more" and "locked more" key labels. To achieve these behavior, we should initialize
+ // 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 == KeyboardId.MODE_PHONE ? R.xml.kbd_phone : R.xml.kbd_symbols,
+ colorScheme, hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true);
+ mSymbolsShiftedId = new KeyboardId(locale, orientation, mode,
+ mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone_symbols : R.xml.kbd_symbols_shift,
+ colorScheme, hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true);
+ }
+
+ private boolean hasVoiceKey(boolean isSymbols) {
+ return mVoiceKeyEnabled && (isSymbols != mVoiceButtonOnPrimary);
+ }
+
+ public void loadKeyboard(int mode, int imeOptions, boolean voiceKeyEnabled,
+ boolean voiceButtonOnPrimary) {
+ mSymbolsModeState = SYMBOLS_MODE_STATE_NONE;
+ try {
+ loadKeyboardInternal(mode, imeOptions, voiceKeyEnabled, voiceButtonOnPrimary,
+ false);
+ } catch (RuntimeException e) {
+ Log.w(TAG, e);
+ LatinImeLogger.logOnException(mode + "," + imeOptions, e);
+ }
+ }
+
+ private void loadKeyboardInternal(int mode, int imeOptions, boolean voiceButtonEnabled,
+ boolean voiceButtonOnPrimary, boolean isSymbols) {
+ if (mInputView == null) return;
+ mInputView.setPreviewEnabled(mInputMethodService.getPopupOn());
+
+ mMode = mode;
+ mImeOptions = imeOptions;
+ mVoiceKeyEnabled = voiceButtonEnabled;
+ mVoiceButtonOnPrimary = voiceButtonOnPrimary;
+ mIsSymbols = isSymbols;
+ // Update the settings key state because number of enabled IMEs could have been changed
+ mHasSettingsKey = getSettingsKeyMode(mPrefs, mInputMethodService);
+ makeSymbolsKeyboardIds();
+
+ KeyboardId id = getKeyboardId(mode, imeOptions, isSymbols);
+ LatinKeyboard keyboard = getKeyboard(id);
+
+ mCurrentId = id;
+ mInputView.setKeyboard(keyboard);
+ }
+
+ private LatinKeyboard getKeyboard(KeyboardId id) {
+ final SoftReference<LatinKeyboard> ref = mKeyboardCache.get(id);
+ LatinKeyboard keyboard = (ref == null) ? null : ref.get();
+ if (keyboard == null) {
+ final Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(
+ mSubtypeSwitcher.getInputLocale());
+
+ keyboard = new LatinKeyboard(mInputMethodService, id);
+
+ if (id.mEnableShiftLock) {
+ keyboard.enableShiftLock();
+ }
+
+ mKeyboardCache.put(id, new SoftReference<LatinKeyboard>(keyboard));
+ if (DEBUG)
+ Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": "
+ + ((ref == null) ? "LOAD" : "GCed") + " id=" + id);
+
+ mSubtypeSwitcher.changeSystemLocale(savedLocale);
+ } else if (DEBUG) {
+ Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": HIT id=" + id);
+ }
+
+ keyboard.onAutoCompletionStateChanged(mIsAutoCompletionActive);
+ keyboard.setShifted(false);
+ return keyboard;
+ }
+
+ private KeyboardId getKeyboardId(int mode, int imeOptions, boolean isSymbols) {
+ final boolean hasVoiceKey = hasVoiceKey(isSymbols);
+ final int charColorId = getColorScheme();
+ final int xmlId;
+ final boolean enableShiftLock;
+
+ if (isSymbols) {
+ if (mode == KeyboardId.MODE_PHONE) {
+ xmlId = R.xml.kbd_phone_symbols;
+ } else if (mode == KeyboardId.MODE_NUMBER) {
+ // Note: MODE_NUMBER keyboard layout has no "switch alpha symbol" key.
+ xmlId = R.xml.kbd_number;
+ } else {
+ xmlId = R.xml.kbd_symbols;
+ }
+ enableShiftLock = false;
+ } else {
+ if (mode == KeyboardId.MODE_PHONE) {
+ xmlId = R.xml.kbd_phone;
+ enableShiftLock = false;
+ } else if (mode == KeyboardId.MODE_NUMBER) {
+ xmlId = R.xml.kbd_number;
+ enableShiftLock = false;
+ } else {
+ xmlId = R.xml.kbd_qwerty;
+ enableShiftLock = true;
+ }
+ }
+ final int orientation = mInputMethodService.getResources().getConfiguration().orientation;
+ final Locale locale = mSubtypeSwitcher.getInputLocale();
+ return new KeyboardId(locale, orientation, mode, xmlId, charColorId,
+ mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, imeOptions, enableShiftLock);
+ }
+
+ public int getKeyboardMode() {
+ return mMode;
+ }
+
+ public boolean isAlphabetMode() {
+ return mCurrentId != null && mCurrentId.isAlphabetKeyboard();
+ }
+
+ public boolean isInputViewShown() {
+ return mInputView != null && mInputView.isShown();
+ }
+
+ public boolean isKeyboardAvailable() {
+ if (mInputView != null)
+ return mInputView.getLatinKeyboard() != null;
+ return false;
+ }
+
+ private LatinKeyboard getLatinKeyboard() {
+ if (mInputView != null)
+ return mInputView.getLatinKeyboard();
+ return null;
+ }
+
+ public void setPreferredLetters(int[] frequencies) {
+ LatinKeyboard latinKeyboard = getLatinKeyboard();
+ if (latinKeyboard != null)
+ latinKeyboard.setPreferredLetters(frequencies);
+ }
+
+ public void keyReleased() {
+ LatinKeyboard latinKeyboard = getLatinKeyboard();
+ if (latinKeyboard != null)
+ latinKeyboard.keyReleased();
+ }
+
+ public boolean isShiftedOrShiftLocked() {
+ LatinKeyboard latinKeyboard = getLatinKeyboard();
+ if (latinKeyboard != null)
+ return latinKeyboard.isShiftedOrShiftLocked();
+ return false;
+ }
+
+ public boolean isShiftLocked() {
+ LatinKeyboard latinKeyboard = getLatinKeyboard();
+ if (latinKeyboard != null)
+ return latinKeyboard.isShiftLocked();
+ return false;
+ }
+
+ public boolean isAutomaticTemporaryUpperCase() {
+ LatinKeyboard latinKeyboard = getLatinKeyboard();
+ if (latinKeyboard != null)
+ return latinKeyboard.isAutomaticTemporaryUpperCase();
+ return false;
+ }
+
+ public boolean isManualTemporaryUpperCase() {
+ LatinKeyboard latinKeyboard = getLatinKeyboard();
+ if (latinKeyboard != null)
+ return latinKeyboard.isManualTemporaryUpperCase();
+ return false;
+ }
+
+ private void setManualTemporaryUpperCase(boolean shifted) {
+ LatinKeyboard latinKeyboard = getLatinKeyboard();
+ if (latinKeyboard != null) {
+ // On non-distinct multi touch panel device, we should also turn off the shift locked
+ // state when shift key is pressed to go to normal mode.
+ // On the other hand, on distinct multi touch panel device, turning off the shift locked
+ // state with shift key pressing is handled by onReleaseShift().
+ if (!hasDistinctMultitouch() && !shifted && latinKeyboard.isShiftLocked()) {
+ latinKeyboard.setShiftLocked(false);
+ }
+ if (latinKeyboard.setShifted(shifted)) {
+ mInputView.invalidateAllKeys();
+ }
+ }
+ }
+
+ private void setShiftLocked(boolean shiftLocked) {
+ LatinKeyboard latinKeyboard = getLatinKeyboard();
+ if (latinKeyboard != null && latinKeyboard.setShiftLocked(shiftLocked)) {
+ mInputView.invalidateAllKeys();
+ }
+ }
+
+ /**
+ * Toggle keyboard shift state triggered by user touch event.
+ */
+ public void toggleShift() {
+ mInputMethodService.mHandler.cancelUpdateShiftState();
+ if (DEBUG_STATE)
+ Log.d(TAG, "toggleShift:"
+ + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
+ + " shiftKeyState=" + mShiftKeyState);
+ if (isAlphabetMode()) {
+ setManualTemporaryUpperCase(!isShiftedOrShiftLocked());
+ } else {
+ toggleShiftInSymbol();
+ }
+ }
+
+ public void toggleCapsLock() {
+ mInputMethodService.mHandler.cancelUpdateShiftState();
+ if (DEBUG_STATE)
+ Log.d(TAG, "toggleCapsLock:"
+ + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
+ + " shiftKeyState=" + mShiftKeyState);
+ if (isAlphabetMode()) {
+ if (isShiftLocked()) {
+ // Shift key is long pressed while caps lock state, we will toggle back to normal
+ // state. And mark as if shift key is released.
+ setShiftLocked(false);
+ mShiftKeyState.onRelease();
+ } else {
+ setShiftLocked(true);
+ }
+ }
+ }
+
+ private void setAutomaticTemporaryUpperCase() {
+ LatinKeyboard latinKeyboard = getLatinKeyboard();
+ if (latinKeyboard != null) {
+ latinKeyboard.setAutomaticTemporaryUpperCase();
+ mInputView.invalidateAllKeys();
+ }
+ }
+
+ /**
+ * Update keyboard shift state triggered by connected EditText status change.
+ */
+ public void updateShiftState() {
+ final ShiftKeyState shiftKeyState = mShiftKeyState;
+ if (DEBUG_STATE)
+ Log.d(TAG, "updateShiftState:"
+ + " autoCaps=" + mInputMethodService.getCurrentAutoCapsState()
+ + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
+ + " shiftKeyState=" + shiftKeyState);
+ if (isAlphabetMode()) {
+ if (!isShiftLocked() && !shiftKeyState.isIgnoring()) {
+ if (shiftKeyState.isReleasing() && mInputMethodService.getCurrentAutoCapsState()) {
+ // Only when shift key is releasing, automatic temporary upper case will be set.
+ setAutomaticTemporaryUpperCase();
+ } else {
+ setManualTemporaryUpperCase(shiftKeyState.isMomentary());
+ }
+ }
+ } else {
+ // In symbol keyboard mode, we should clear shift key state because only alphabet
+ // keyboard has shift key.
+ shiftKeyState.onRelease();
+ }
+ }
+
+ public void changeKeyboardMode() {
+ if (DEBUG_STATE)
+ Log.d(TAG, "changeKeyboardMode:"
+ + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
+ + " shiftKeyState=" + mShiftKeyState);
+ toggleKeyboardMode();
+ if (isShiftLocked() && isAlphabetMode())
+ setShiftLocked(true);
+ updateShiftState();
+ }
+
+ public void onPressShift() {
+ if (!isKeyboardAvailable())
+ return;
+ ShiftKeyState shiftKeyState = mShiftKeyState;
+ if (DEBUG_STATE)
+ Log.d(TAG, "onPressShift:"
+ + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
+ + " shiftKeyState=" + shiftKeyState);
+ if (isAlphabetMode()) {
+ if (isShiftLocked()) {
+ // Shift key is pressed while caps lock state, we will treat this state as shifted
+ // caps lock state and mark as if shift key pressed while normal state.
+ shiftKeyState.onPress();
+ setManualTemporaryUpperCase(true);
+ } else if (isAutomaticTemporaryUpperCase()) {
+ // Shift key is pressed while automatic temporary upper case, we have to move to
+ // manual temporary upper case.
+ shiftKeyState.onPress();
+ setManualTemporaryUpperCase(true);
+ } else if (isShiftedOrShiftLocked()) {
+ // In manual upper case state, we just record shift key has been pressing while
+ // shifted state.
+ shiftKeyState.onPressOnShifted();
+ } else {
+ // In base layout, chording or manual temporary upper case mode is started.
+ shiftKeyState.onPress();
+ toggleShift();
+ }
+ } else {
+ // In symbol mode, just toggle symbol and symbol more keyboard.
+ shiftKeyState.onPress();
+ toggleShift();
+ }
+ }
+
+ public void onReleaseShift() {
+ if (!isKeyboardAvailable())
+ return;
+ ShiftKeyState shiftKeyState = mShiftKeyState;
+ if (DEBUG_STATE)
+ Log.d(TAG, "onReleaseShift:"
+ + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
+ + " shiftKeyState=" + shiftKeyState);
+ if (isAlphabetMode()) {
+ if (shiftKeyState.isMomentary()) {
+ // After chording input while normal state.
+ toggleShift();
+ } else if (isShiftLocked() && !shiftKeyState.isIgnoring()) {
+ // Shift has been pressed without chording while caps lock state.
+ toggleCapsLock();
+ } else if (isShiftedOrShiftLocked() && shiftKeyState.isPressingOnShifted()) {
+ // Shift has been pressed without chording while shifted state.
+ toggleShift();
+ }
+ }
+ shiftKeyState.onRelease();
+ }
+
+ public void onPressSymbol() {
+ if (DEBUG_STATE)
+ Log.d(TAG, "onReleaseShift:"
+ + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
+ + " symbolKeyState=" + mSymbolKeyState);
+ changeKeyboardMode();
+ mSymbolKeyState.onPress();
+ }
+
+ public void onReleaseSymbol() {
+ if (DEBUG_STATE)
+ Log.d(TAG, "onReleaseShift:"
+ + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
+ + " symbolKeyState=" + mSymbolKeyState);
+ if (mSymbolKeyState.isMomentary())
+ changeKeyboardMode();
+ mSymbolKeyState.onRelease();
+ }
+
+ public void onOtherKeyPressed() {
+ if (DEBUG_STATE)
+ Log.d(TAG, "onOtherKeyPressed:"
+ + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
+ + " shiftKeyState=" + mShiftKeyState
+ + " symbolKeyState=" + mSymbolKeyState);
+ mShiftKeyState.onOtherKeyPressed();
+ mSymbolKeyState.onOtherKeyPressed();
+ }
+
+ private void toggleShiftInSymbol() {
+ if (isAlphabetMode())
+ return;
+ final LatinKeyboard keyboard;
+ if (mCurrentId.equals(mSymbolsId) || !mCurrentId.equals(mSymbolsShiftedId)) {
+ mCurrentId = mSymbolsShiftedId;
+ keyboard = getKeyboard(mCurrentId);
+ // Symbol shifted keyboard has an ALT key that has a caps lock style indicator. To
+ // enable the indicator, we need to call enableShiftLock() and setShiftLocked(true).
+ // Thus we can keep the ALT key's Key.on value true while LatinKey.onRelease() is
+ // called.
+ keyboard.setShiftLocked(true);
+ } else {
+ mCurrentId = mSymbolsId;
+ keyboard = getKeyboard(mCurrentId);
+ // Symbol keyboard has an ALT key that has a caps lock style indicator. To disable the
+ // indicator, we need to call enableShiftLock() and setShiftLocked(false).
+ keyboard.setShifted(false);
+ }
+ mInputView.setKeyboard(keyboard);
+ }
+
+ private void toggleKeyboardMode() {
+ loadKeyboardInternal(mMode, mImeOptions, mVoiceKeyEnabled, mVoiceButtonOnPrimary,
+ !mIsSymbols);
+ if (mIsSymbols) {
+ mSymbolsModeState = SYMBOLS_MODE_STATE_BEGIN;
+ } else {
+ mSymbolsModeState = SYMBOLS_MODE_STATE_NONE;
+ }
+ }
+
+ public boolean hasDistinctMultitouch() {
+ return mInputView != null && mInputView.hasDistinctMultitouch();
+ }
+
+ /**
+ * Updates state machine to figure out when to automatically switch back to alpha mode.
+ */
+ public void onKey(int key) {
+ // Switch back to alpha mode if user types one or more non-space/enter
+ // characters followed by a space/enter
+ switch (mSymbolsModeState) {
+ case SYMBOLS_MODE_STATE_BEGIN:
+ if (key != Keyboard.CODE_SPACE && key != Keyboard.CODE_ENTER && key > 0) {
+ mSymbolsModeState = SYMBOLS_MODE_STATE_SYMBOL;
+ }
+ break;
+ case SYMBOLS_MODE_STATE_SYMBOL:
+ if (key == Keyboard.CODE_ENTER || key == Keyboard.CODE_SPACE) {
+ changeKeyboardMode();
+ }
+ break;
+ }
+ }
+
+ public LatinKeyboardView getInputView() {
+ return mInputView;
+ }
+
+ public LatinKeyboardView onCreateInputView() {
+ createInputViewInternal(mLayoutId, true);
+ return mInputView;
+ }
+
+ private void createInputViewInternal(int newLayout, boolean forceReset) {
+ if (mLayoutId != newLayout || mInputView == null || forceReset) {
+ if (mInputView != null) {
+ mInputView.closing();
+ }
+ if (THEMES.length <= newLayout) {
+ newLayout = Integer.valueOf(DEFAULT_LAYOUT_ID);
+ }
+
+ LatinIMEUtil.GCUtils.getInstance().reset();
+ boolean tryGC = true;
+ for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
+ try {
+ mInputView = (LatinKeyboardView) mInputMethodService.getLayoutInflater(
+ ).inflate(THEMES[newLayout], null);
+ tryGC = false;
+ } catch (OutOfMemoryError e) {
+ Log.w(TAG, "load keyboard failed: " + e);
+ tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(
+ mLayoutId + "," + newLayout, e);
+ } catch (InflateException e) {
+ Log.w(TAG, "load keyboard failed: " + e);
+ tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(
+ mLayoutId + "," + newLayout, e);
+ }
+ }
+ mInputView.setOnKeyboardActionListener(mInputMethodService);
+ mLayoutId = newLayout;
+ }
+ }
+
+ private void postSetInputView() {
+ mInputMethodService.mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mInputView != null) {
+ mInputMethodService.setInputView(mInputView);
+ }
+ mInputMethodService.updateInputViewShown();
+ }
+ });
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (PREF_KEYBOARD_LAYOUT.equals(key)) {
+ final int layoutId = Integer.valueOf(
+ sharedPreferences.getString(key, DEFAULT_LAYOUT_ID));
+ createInputViewInternal(layoutId, false);
+ postSetInputView();
+ } else if (LatinIMESettings.PREF_SETTINGS_KEY.equals(key)) {
+ mHasSettingsKey = getSettingsKeyMode(sharedPreferences, mInputMethodService);
+ createInputViewInternal(mLayoutId, true);
+ postSetInputView();
+ }
+ }
+
+ private int getColorScheme() {
+ return (mInputView != null)
+ ? mInputView.getColorScheme() : KeyboardView.COLOR_SCHEME_WHITE;
+ }
+
+ public void onAutoCompletionStateChanged(boolean isAutoCompletion) {
+ if (isAutoCompletion != mIsAutoCompletionActive) {
+ LatinKeyboardView keyboardView = getInputView();
+ mIsAutoCompletionActive = isAutoCompletion;
+ keyboardView.invalidateKey(((LatinKeyboard) keyboardView.getKeyboard())
+ .onAutoCompletionStateChanged(isAutoCompletion));
+ }
+ }
+
+ private static boolean getSettingsKeyMode(SharedPreferences prefs, Context context) {
+ Resources resources = context.getResources();
+ final boolean showSettingsKeyOption = resources.getBoolean(
+ R.bool.config_enable_show_settings_key_option);
+ if (showSettingsKeyOption) {
+ final String settingsKeyMode = prefs.getString(LatinIMESettings.PREF_SETTINGS_KEY,
+ resources.getString(DEFAULT_SETTINGS_KEY_MODE));
+ // We show the settings key when 1) SETTINGS_KEY_MODE_ALWAYS_SHOW or
+ // 2) SETTINGS_KEY_MODE_AUTO and there are two or more enabled IMEs on the system
+ if (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_ALWAYS_SHOW))
+ || (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_AUTO))
+ && LatinIMEUtil.hasMultipleEnabledIMEsOrSubtypes(
+ ((InputMethodManager) context.getSystemService(
+ Context.INPUT_METHOD_SERVICE))))) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index b1ef08573..c54b1b459 100644
--- a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -14,7 +14,11 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+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;
@@ -29,8 +33,6 @@ import android.graphics.Rect;
import android.graphics.Region.Op;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
-import android.inputmethodservice.Keyboard;
-import android.inputmethodservice.Keyboard.Key;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
@@ -43,132 +45,56 @@ 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.
+ * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and detecting key
+ * presses and touch movements.
*
- * @attr ref R.styleable#LatinKeyboardBaseView_keyBackground
- * @attr ref R.styleable#LatinKeyboardBaseView_keyPreviewLayout
- * @attr ref R.styleable#LatinKeyboardBaseView_keyPreviewOffset
- * @attr ref R.styleable#LatinKeyboardBaseView_labelTextSize
- * @attr ref R.styleable#LatinKeyboardBaseView_keyTextSize
- * @attr ref R.styleable#LatinKeyboardBaseView_keyTextColor
- * @attr ref R.styleable#LatinKeyboardBaseView_verticalCorrection
- * @attr ref R.styleable#LatinKeyboardBaseView_popupLayout
+ * @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 LatinKeyboardBaseView extends View implements PointerTracker.UIProxy {
- private static final String TAG = "LatinKeyboardBaseView";
+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 NOT_A_TOUCH_COORDINATE = -1;
+ private static final boolean ENABLE_CAPSLOCK_BY_LONGPRESS = false;
+ private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true;
- 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();
- }
+ 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
- /* 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 NUMBER_HINT_VERTICAL_ADJUSTMENT_PIXEL = -1;
+ private static final int HINT_ICON_VERTICAL_ADJUSTMENT_PIXEL = -1;
// XML attribute
- private int mKeyTextSize;
+ private int mKeyLetterSize;
private int mKeyTextColor;
- private Typeface mKeyTextStyle = Typeface.DEFAULT;
+ private int mKeyTextColorDisabled;
+ private Typeface mKeyLetterStyle = Typeface.DEFAULT;
private int mLabelTextSize;
- private int mSymbolColorScheme = 0;
+ private int mColorScheme = COLOR_SCHEME_WHITE;
private int mShadowColor;
private float mShadowRadius;
private Drawable mKeyBackground;
@@ -182,15 +108,14 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
// Main keyboard
private Keyboard mKeyboard;
private Key[] mKeys;
- // TODO this attribute should be gotten from Keyboard.
- private int mKeyboardVerticalGap;
// 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 int mOldPreviewKeyIndex = KeyDetector.NOT_A_KEY;
private boolean mShowPreview = true;
private boolean mShowTouchPoints = true;
private int mPopupPreviewOffsetX;
@@ -202,7 +127,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
// Popup mini keyboard
private PopupWindow mMiniKeyboardPopup;
- private LatinKeyboardBaseView mMiniKeyboard;
+ private KeyboardView mMiniKeyboard;
private View mMiniKeyboardParent;
private final WeakHashMap<Key, View> mMiniKeyboardCache = new WeakHashMap<Key, View>();
private int mMiniKeyboardOriginX;
@@ -212,13 +137,13 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
private final float mMiniKeyboardSlideAllowance;
private int mMiniKeyboardTrackerId;
- /** Listener for {@link OnKeyboardActionListener}. */
- private OnKeyboardActionListener mKeyboardActionListener;
+ /** Listener for {@link KeyboardActionListener}. */
+ private KeyboardActionListener mKeyboardActionListener;
private final ArrayList<PointerTracker> mPointerTrackers = new ArrayList<PointerTracker>();
// TODO: Let the PointerTracker class manage this pointer queue
- private final PointerQueue mPointerQueue = new PointerQueue();
+ private final PointerTrackerQueue mPointerQueue = new PointerTrackerQueue();
private final boolean mHasDistinctMultitouch;
private int mOldPointerCount = 1;
@@ -248,9 +173,15 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
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<Integer, Integer> mTextHeightCache = new HashMap<Integer, Integer>();
- // Distance from horizontal center of the key, proportional to key label text height.
- private final float KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR = 0.55f;
- private final String KEY_LABEL_HEIGHT_REFERENCE_CHAR = "H";
+ // 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();
@@ -259,6 +190,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
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;
@@ -282,6 +214,11 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
openPopupIfRequired(msg.arg1, tracker);
break;
}
+ case MSG_LONGPRESS_SHIFT_KEY: {
+ final PointerTracker tracker = (PointerTracker)msg.obj;
+ onLongPressShiftKey(tracker);
+ break;
+ }
}
}
@@ -325,17 +262,26 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
}
public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {
- removeMessages(MSG_LONGPRESS_KEY);
+ cancelLongPressTimers();
sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay);
}
- public void cancelLongPressTimer() {
+ public void startLongPressShiftTimer(long delay, int keyIndex, PointerTracker tracker) {
+ cancelLongPressTimers();
+ if (ENABLE_CAPSLOCK_BY_LONGPRESS) {
+ 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();
- cancelLongPressTimer();
+ cancelLongPressTimers();
}
public void cancelAllMessages() {
@@ -345,63 +291,15 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
}
};
- static class PointerQueue {
- private LinkedList<PointerTracker> mQueue = new LinkedList<PointerTracker>();
-
- public void add(PointerTracker tracker) {
- mQueue.add(tracker);
- }
-
- public int lastIndexOf(PointerTracker tracker) {
- LinkedList<PointerTracker> 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<PointerTracker> 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 LatinKeyboardBaseView(Context context, AttributeSet attrs) {
+ public KeyboardView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.keyboardViewStyle);
}
- public LatinKeyboardBaseView(Context context, AttributeSet attrs, int defStyle) {
+ public KeyboardView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(
- attrs, R.styleable.LatinKeyboardBaseView, defStyle, R.style.LatinKeyboardBaseView);
+ attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
LayoutInflater inflate =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
int previewLayout = 0;
@@ -413,63 +311,54 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
int attr = a.getIndex(i);
switch (attr) {
- case R.styleable.LatinKeyboardBaseView_keyBackground:
+ case R.styleable.KeyboardView_keyBackground:
mKeyBackground = a.getDrawable(attr);
break;
- case R.styleable.LatinKeyboardBaseView_keyHysteresisDistance:
+ case R.styleable.KeyboardView_keyHysteresisDistance:
mKeyHysteresisDistance = a.getDimensionPixelOffset(attr, 0);
break;
- case R.styleable.LatinKeyboardBaseView_verticalCorrection:
+ case R.styleable.KeyboardView_verticalCorrection:
mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
break;
- case R.styleable.LatinKeyboardBaseView_keyPreviewLayout:
+ case R.styleable.KeyboardView_keyPreviewLayout:
previewLayout = a.getResourceId(attr, 0);
break;
- case R.styleable.LatinKeyboardBaseView_keyPreviewOffset:
+ case R.styleable.KeyboardView_keyPreviewOffset:
mPreviewOffset = a.getDimensionPixelOffset(attr, 0);
break;
- case R.styleable.LatinKeyboardBaseView_keyPreviewHeight:
+ case R.styleable.KeyboardView_keyPreviewHeight:
mPreviewHeight = a.getDimensionPixelSize(attr, 80);
break;
- case R.styleable.LatinKeyboardBaseView_keyTextSize:
- mKeyTextSize = a.getDimensionPixelSize(attr, 18);
+ case R.styleable.KeyboardView_keyLetterSize:
+ mKeyLetterSize = a.getDimensionPixelSize(attr, 18);
break;
- case R.styleable.LatinKeyboardBaseView_keyTextColor:
+ case R.styleable.KeyboardView_keyTextColor:
mKeyTextColor = a.getColor(attr, 0xFF000000);
break;
- case R.styleable.LatinKeyboardBaseView_labelTextSize:
+ 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.LatinKeyboardBaseView_popupLayout:
+ case R.styleable.KeyboardView_popupLayout:
mPopupLayout = a.getResourceId(attr, 0);
break;
- case R.styleable.LatinKeyboardBaseView_shadowColor:
+ case R.styleable.KeyboardView_shadowColor:
mShadowColor = a.getColor(attr, 0);
break;
- case R.styleable.LatinKeyboardBaseView_shadowRadius:
+ case R.styleable.KeyboardView_shadowRadius:
mShadowRadius = a.getFloat(attr, 0f);
break;
// TODO: Use Theme (android.R.styleable.Theme_backgroundDimAmount)
- case R.styleable.LatinKeyboardBaseView_backgroundDimAmount:
+ case R.styleable.KeyboardView_backgroundDimAmount:
mBackgroundDimAmount = a.getFloat(attr, 0.5f);
break;
- //case android.R.styleable.
- case R.styleable.LatinKeyboardBaseView_keyTextStyle:
- int textStyle = a.getInt(attr, 0);
- switch (textStyle) {
- case 0:
- mKeyTextStyle = Typeface.DEFAULT;
- break;
- case 1:
- mKeyTextStyle = Typeface.DEFAULT_BOLD;
- break;
- default:
- mKeyTextStyle = Typeface.defaultFromStyle(textStyle);
- break;
- }
+ case R.styleable.KeyboardView_keyLetterStyle:
+ mKeyLetterStyle = Typeface.defaultFromStyle(a.getInt(attr, Typeface.NORMAL));
break;
- case R.styleable.LatinKeyboardBaseView_symbolColorScheme:
- mSymbolColorScheme = a.getInt(attr, 0);
+ case R.styleable.KeyboardView_colorScheme:
+ mColorScheme = a.getInt(attr, COLOR_SCHEME_WHITE);
break;
}
}
@@ -489,6 +378,8 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
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);
@@ -511,6 +402,8 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
GestureDetector.SimpleOnGestureListener listener =
new GestureDetector.SimpleOnGestureListener() {
+ private boolean mProcessingDoubleTapEvent = false;
+
@Override
public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX,
float velocityY) {
@@ -546,6 +439,28 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
}
return false;
}
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ if (ENABLE_CAPSLOCK_BY_DOUBLETAP && mKeyboard instanceof LatinKeyboard
+ && ((LatinKeyboard) mKeyboard).isAlphaKeyboard()) {
+ final int pointerIndex = e.getActionIndex();
+ final int id = e.getPointerId(pointerIndex);
+ final PointerTracker tracker = getPointerTracker(id);
+ if (tracker.isOnShiftKey((int)e.getX(), (int)e.getY())) {
+ onDoubleTapShiftKey(tracker);
+ mProcessingDoubleTapEvent = true;
+ return true;
+ }
+ }
+ mProcessingDoubleTapEvent = false;
+ return false;
+ }
+
+ @Override
+ public boolean onDoubleTapEvent(MotionEvent e) {
+ return mProcessingDoubleTapEvent;
+ }
};
final boolean ignoreMultitouch = true;
@@ -557,7 +472,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval);
}
- public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
+ public void setOnKeyboardActionListener(KeyboardActionListener listener) {
mKeyboardActionListener = listener;
for (PointerTracker tracker : mPointerTrackers) {
tracker.setOnKeyboardActionListener(listener);
@@ -565,10 +480,10 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
}
/**
- * Returns the {@link OnKeyboardActionListener} object.
+ * Returns the {@link KeyboardActionListener} object.
* @return the listener attached to this keyboard
*/
- protected OnKeyboardActionListener getOnKeyboardActionListener() {
+ protected KeyboardActionListener getOnKeyboardActionListener() {
return mKeyboardActionListener;
}
@@ -590,15 +505,14 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
LatinImeLogger.onSetKeyboard(keyboard);
mKeys = mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
-getPaddingTop() + mVerticalCorrection);
- mKeyboardVerticalGap = (int)getResources().getDimension(R.dimen.key_bottom_gap);
for (PointerTracker tracker : mPointerTrackers) {
- tracker.setKeyboard(mKeys, mKeyHysteresisDistance);
+ tracker.setKeyboard(keyboard, mKeys, mKeyHysteresisDistance);
}
requestLayout();
// Hint to reallocate the buffer if the size changed
mKeyboardChanged = true;
invalidateAllKeys();
- computeProximityThreshold(keyboard);
+ computeProximityThreshold(keyboard, mKeys);
mMiniKeyboardCache.clear();
}
@@ -615,39 +529,12 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
* 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;
}
/**
- * Sets the state of the shift key of the keyboard, if any.
- * @param shifted whether or not to enable the state of the shift key
- * @return true if the shift key state changed, false if there was no change
- */
- public boolean setShifted(boolean shifted) {
- if (mKeyboard != null) {
- if (mKeyboard.setShifted(shifted)) {
- // The whole keyboard probably needs to be redrawn
- invalidateAllKeys();
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns the state of the shift key of the keyboard, if any.
- * @return true if the shift is in a pressed state, false otherwise. If there is
- * no shift key on the keyboard or there is no keyboard attached, it returns false.
- */
- public boolean isShifted() {
- if (mKeyboard != null) {
- return mKeyboard.isShifted();
- }
- return false;
- }
-
- /**
* 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
@@ -666,8 +553,8 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
return mShowPreview;
}
- public int getSymbolColorScheme() {
- return mSymbolColorScheme;
+ public int getColorScheme() {
+ return mColorScheme;
}
public void setPopupParent(View v) {
@@ -681,7 +568,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
}
/**
- * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key
+ * 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
@@ -698,7 +585,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
}
protected CharSequence adjustCase(CharSequence label) {
- if (mKeyboard.isShifted() && label != null && label.length() < 3
+ if (mKeyboard.isShiftedOrShiftLocked() && label != null && label.length() < 3
&& Character.isLowerCase(label.charAt(0))) {
label = label.toString().toUpperCase();
}
@@ -722,23 +609,27 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
}
/**
- * Compute the average distance between adjacent keys (horizontally and vertically)
- * and square it to get the proximity threshold. We use a square here and in computing
- * the touch distance from a key's center to avoid taking a square root.
+ * Compute the most common key width and use it as proximity key detection threshold.
* @param keyboard
+ * @param keys
*/
- private void computeProximityThreshold(Keyboard keyboard) {
- if (keyboard == null) return;
- final Key[] keys = mKeys;
- if (keys == null) return;
- int length = keys.length;
- int dimensionSum = 0;
- for (int i = 0; i < length; i++) {
- Key key = keys[i];
- dimensionSum += Math.min(key.width, key.height + mKeyboardVerticalGap) + key.gap;
+ private void computeProximityThreshold(Keyboard keyboard, Key[] keys) {
+ if (keyboard == null || keys == null || keys.length == 0) return;
+ final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>();
+ int maxCount = 0;
+ int mostCommonWidth = 0;
+ for (Key key : keys) {
+ final Integer width = key.mWidth + key.mGap;
+ Integer count = histogram.get(width);
+ if (count == null)
+ count = 0;
+ histogram.put(width, ++count);
+ if (count > maxCount) {
+ maxCount = count;
+ mostCommonWidth = width;
+ }
}
- if (dimensionSum < 0 || length == 0) return;
- mKeyDetector.setProximityThreshold((int) (dimensionSum * 1.4f / length));
+ mKeyDetector.setProximityThreshold(mostCommonWidth);
}
@Override
@@ -757,6 +648,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
canvas.drawBitmap(mBuffer, 0, 0, null);
}
+ @SuppressWarnings("unused")
private void onBufferDraw() {
if (mBuffer == null || mKeyboardChanged) {
if (mBuffer == null || mKeyboardChanged &&
@@ -783,16 +675,16 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
final int kbdPaddingTop = getPaddingTop();
final Key[] keys = mKeys;
final Key invalidKey = mInvalidatedKey;
+ final boolean isManualTemporaryUpperCase = mKeyboard.isManualTemporaryUpperCase();
- paint.setColor(mKeyTextColor);
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) {
+ if (invalidKey.mX + kbdPaddingLeft - 1 <= clipRegion.left &&
+ invalidKey.mY + kbdPaddingTop - 1 <= clipRegion.top &&
+ invalidKey.mX + invalidKey.mWidth + kbdPaddingLeft + 1 >= clipRegion.right &&
+ invalidKey.mY + invalidKey.mHeight + kbdPaddingTop + 1 >= clipRegion.bottom) {
drawSingleKey = true;
}
}
@@ -807,77 +699,122 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
keyBackground.setState(drawableState);
// Switch the character to uppercase if shift is pressed
- String label = key.label == null? null : adjustCase(key.label).toString();
+ String label = key.mLabel == null? null : adjustCase(key.mLabel).toString();
final Rect bounds = keyBackground.getBounds();
- if (key.width != bounds.right || key.height != bounds.bottom) {
- keyBackground.setBounds(0, 0, key.width, key.height);
+ if (key.mWidth != bounds.right || key.mHeight != bounds.bottom) {
+ keyBackground.setBounds(0, 0, key.mWidth, key.mHeight);
}
- canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
+ canvas.translate(key.mX + kbdPaddingLeft, key.mY + kbdPaddingTop);
keyBackground.draw(canvas);
- boolean shouldDrawIcon = true;
+ final int rowHeight = padding.top + key.mHeight;
+ // Draw key label
if (label != null) {
// For characters, use large font. For labels like "Done", use small font.
- final int labelSize;
- if (label.length() > 1 && key.codes.length < 2) {
- labelSize = mLabelTextSize;
- paint.setTypeface(Typeface.DEFAULT_BOLD);
+ final int labelSize = getLabelSizeAndSetPaint(label, key, paint);
+ final int labelCharHeight = getLabelCharHeight(labelSize, paint);
+
+ // Vertical label text alignment.
+ final float baseline;
+ if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_BOTTOM) != 0) {
+ baseline = key.mHeight -
+ + labelCharHeight * KEY_LABEL_VERTICAL_PADDING_FACTOR;
+ if (DEBUG_SHOW_ALIGN)
+ drawHorizontalLine(canvas, (int)baseline, key.mWidth, 0xc0008000,
+ new Paint());
+ } else { // Align center
+ final float centerY = (key.mHeight + padding.top - padding.bottom) / 2;
+ baseline = centerY
+ + labelCharHeight * KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR_CENTER;
+ }
+ // Horizontal label text alignment
+ final int positionX;
+ if ((key.mLabelOption & 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.mLabelOption & KEY_LABEL_OPTION_ALIGN_RIGHT) != 0) {
+ positionX = key.mWidth - mKeyLabelHorizontalPadding - padding.right;
+ paint.setTextAlign(Align.RIGHT);
+ if (DEBUG_SHOW_ALIGN)
+ drawVerticalLine(canvas, positionX, rowHeight, 0xc0808000, new Paint());
} else {
- labelSize = mKeyTextSize;
- paint.setTypeface(mKeyTextStyle);
+ positionX = (key.mWidth + padding.left - padding.right) / 2;
+ paint.setTextAlign(Align.CENTER);
+ if (DEBUG_SHOW_ALIGN && label.length() > 1)
+ drawVerticalLine(canvas, positionX, rowHeight, 0xc0008080, new Paint());
}
- paint.setTextSize(labelSize);
-
- Integer labelHeightValue = mTextHeightCache.get(labelSize);
- final int labelHeight;
- if (labelHeightValue != null) {
- labelHeight = labelHeightValue;
+ if (key.mManualTemporaryUpperCaseHintIcon != null && isManualTemporaryUpperCase) {
+ paint.setColor(mKeyTextColorDisabled);
} else {
- Rect textBounds = new Rect();
- paint.getTextBounds(KEY_LABEL_HEIGHT_REFERENCE_CHAR, 0, 1, textBounds);
- labelHeight = textBounds.height();
- mTextHeightCache.put(labelSize, labelHeight);
+ paint.setColor(mKeyTextColor);
}
-
- // Draw a drop shadow for the text
+ // Set a drop shadow for the text
paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
- final int centerX = (key.width + padding.left - padding.right) / 2;
- final int centerY = (key.height + padding.top - padding.bottom) / 2;
- final float baseline = centerY
- + labelHeight * KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR;
- canvas.drawText(label, centerX, baseline, paint);
+ canvas.drawText(label, positionX, baseline, paint);
// Turn off drop shadow
paint.setShadowLayer(0, 0, 0, 0);
-
- // Usually don't draw icon if label is not null, but we draw icon for the number
- // hint and popup hint.
- shouldDrawIcon = shouldDrawLabelAndIcon(key);
}
- if (key.icon != null && shouldDrawIcon) {
- // Special handing for the upper-right number hint icons
- final int drawableWidth;
- final int drawableHeight;
+ // Draw key icon
+ final Drawable icon = key.getIcon();
+ if (key.mLabel == null && icon != null) {
+ final int drawableWidth = icon.getIntrinsicWidth();
+ final int drawableHeight = icon.getIntrinsicHeight();
final int drawableX;
- final int drawableY;
- if (shouldDrawIconFully(key)) {
- drawableWidth = key.width;
- drawableHeight = key.height;
- drawableX = 0;
- drawableY = NUMBER_HINT_VERTICAL_ADJUSTMENT_PIXEL;
- } else {
- drawableWidth = key.icon.getIntrinsicWidth();
- drawableHeight = key.icon.getIntrinsicHeight();
- drawableX = (key.width + padding.left - padding.right - drawableWidth) / 2;
- drawableY = (key.height + padding.top - padding.bottom - drawableHeight) / 2;
+ final int drawableY = (
+ key.mHeight + padding.top - padding.bottom - drawableHeight) / 2;
+ if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_LEFT) != 0) {
+ drawableX = padding.left + mKeyLabelHorizontalPadding;
+ if (DEBUG_SHOW_ALIGN)
+ drawVerticalLine(canvas, drawableX, rowHeight, 0xc0800080, new Paint());
+ } else if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_RIGHT) != 0) {
+ drawableX = key.mWidth - padding.right - mKeyLabelHorizontalPadding
+ - drawableWidth;
+ if (DEBUG_SHOW_ALIGN)
+ drawVerticalLine(canvas, drawableX + drawableWidth, rowHeight,
+ 0xc0808000, new Paint());
+ } else { // Align center
+ drawableX = (key.mWidth + padding.left - padding.right - drawableWidth) / 2;
+ if (DEBUG_SHOW_ALIGN)
+ drawVerticalLine(canvas, drawableX + drawableWidth / 2, rowHeight,
+ 0xc0008080, new Paint());
}
- canvas.translate(drawableX, drawableY);
- key.icon.setBounds(0, 0, drawableWidth, drawableHeight);
- key.icon.draw(canvas);
- canvas.translate(-drawableX, -drawableY);
+ drawIcon(canvas, icon, drawableX, drawableY, drawableWidth, drawableHeight);
+ if (DEBUG_SHOW_ALIGN)
+ drawRectangle(canvas, drawableX, drawableY, drawableWidth, drawableHeight,
+ 0x80c00000, new Paint());
}
- canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
+ if (key.mHintIcon != null) {
+ final int drawableWidth = key.mWidth;
+ final int drawableHeight = key.mHeight;
+ final int drawableX = 0;
+ final int drawableY = HINT_ICON_VERTICAL_ADJUSTMENT_PIXEL;
+ Drawable hintIcon = (isManualTemporaryUpperCase
+ && key.mManualTemporaryUpperCaseHintIcon != null)
+ ? key.mManualTemporaryUpperCaseHintIcon : key.mHintIcon;
+ drawIcon(canvas, hintIcon, drawableX, drawableY, drawableWidth, drawableHeight);
+ if (DEBUG_SHOW_ALIGN)
+ drawRectangle(canvas, drawableX, drawableY, drawableWidth, drawableHeight,
+ 0x80c0c000, new Paint());
+ }
+ canvas.translate(-key.mX - kbdPaddingLeft, -key.mY - 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) {
@@ -908,28 +845,98 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
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.mCodes.length < 2) {
+ labelSize = mLabelTextSize;
+ if ((key.mLabelOption & 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.updateKey(NOT_A_KEY);
- showPreview(NOT_A_KEY, null);
+ tracker.releaseKey();
+ showPreview(KeyDetector.NOT_A_KEY, null);
}
+ @Override
public void showPreview(int keyIndex, PointerTracker tracker) {
int oldKeyIndex = mOldPreviewKeyIndex;
mOldPreviewKeyIndex = keyIndex;
- final boolean isLanguageSwitchEnabled = (mKeyboard instanceof LatinKeyboard)
- && ((LatinKeyboard)mKeyboard).isLanguageSwitchEnabled();
// 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)
- || tracker.isSpaceKey(keyIndex) || tracker.isSpaceKey(oldKeyIndex);
+ || (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 && isLanguageSwitchEnabled))) {
- if (keyIndex == NOT_A_KEY) {
+ if (oldKeyIndex != keyIndex && (mShowPreview || (hidePreviewOrShowSpaceKeyPreview))) {
+ if (keyIndex == KeyDetector.NOT_A_KEY) {
mHandler.cancelPopupPreview();
mHandler.dismissPreview(mDelayAfterPreview);
} else if (tracker != null) {
@@ -938,29 +945,35 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
}
}
+ // TODO Must fix popup preview on xlarge layout
private void showKey(final int keyIndex, PointerTracker tracker) {
Key key = tracker.getKey(keyIndex);
- if (key == null)
+ // 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;
- // Should not draw hint icon in key preview
- if (key.icon != null && !shouldDrawLabelAndIcon(key)) {
- mPreviewText.setCompoundDrawables(null, null, null,
- key.iconPreview != null ? key.iconPreview : key.icon);
- mPreviewText.setText(null);
- } else {
+ // What we show as preview should match what we show on key top in onBufferDraw().
+ if (key.mLabel != 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, mKeyTextSize);
+ if (key.mLabel.length() > 1 && key.mCodes.length < 2) {
+ mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyLetterSize);
mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
} else {
mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);
- mPreviewText.setTypeface(mKeyTextStyle);
+ mPreviewText.setTypeface(mKeyLetterStyle);
}
+ } else {
+ final Drawable previewIcon = key.getPreviewIcon();
+ mPreviewText.setCompoundDrawables(null, null, null,
+ previewIcon != null ? previewIcon : key.getIcon());
+ mPreviewText.setText(null);
}
mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width
+ int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.mWidth
+ mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
final int popupHeight = mPreviewHeight;
LayoutParams lp = mPreviewText.getLayoutParams();
@@ -969,8 +982,8 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
lp.height = popupHeight;
}
- int popupPreviewX = key.x - (popupWidth - key.width) / 2;
- int popupPreviewY = key.y - popupHeight + mPreviewOffset;
+ int popupPreviewX = key.mX - (popupWidth - key.mWidth) / 2;
+ int popupPreviewY = key.mY - popupHeight + mPreviewOffset;
mHandler.cancelDismissPreview();
if (mOffsetInWindow == null) {
@@ -984,7 +997,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
}
// Set the preview background state
mPreviewText.getBackground().setState(
- key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
+ key.mPopupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
popupPreviewX += mOffsetInWindow[0];
popupPreviewY += mOffsetInWindow[1];
@@ -992,21 +1005,26 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
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);
+ if (key.mX + key.mWidth <= getWidth() / 2) {
+ popupPreviewX += (int) (key.mWidth * 2.5);
} else {
- popupPreviewX -= (int) (key.width * 2.5);
+ popupPreviewX -= (int) (key.mWidth * 2.5);
}
popupPreviewY += popupHeight;
}
- if (mPreviewPopup.isShowing()) {
- mPreviewPopup.update(popupPreviewX, popupPreviewY, popupWidth, popupHeight);
- } else {
- mPreviewPopup.setWidth(popupWidth);
- mPreviewPopup.setHeight(popupHeight);
- mPreviewPopup.showAtLocation(mMiniKeyboardParent, Gravity.NO_GRAVITY,
- popupPreviewX, popupPreviewY);
+ 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;
@@ -1032,16 +1050,17 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
* @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());
+ mDirtyRect.union(key.mX + getPaddingLeft(), key.mY + getPaddingTop(),
+ key.mX + key.mWidth + getPaddingLeft(), key.mY + key.mHeight + getPaddingTop());
onBufferDraw();
- invalidate(key.x + getPaddingLeft(), key.y + getPaddingTop(),
- key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop());
+ invalidate(key.mX + getPaddingLeft(), key.mY + getPaddingTop(),
+ key.mX + key.mWidth + getPaddingLeft(), key.mY + key.mHeight + getPaddingTop());
}
private boolean openPopupIfRequired(int keyIndex, PointerTracker tracker) {
@@ -1064,42 +1083,64 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
return result;
}
+ private void onLongPressShiftKey(PointerTracker tracker) {
+ tracker.setAlreadyProcessed();
+ mPointerQueue.remove(tracker);
+ mKeyboardActionListener.onKey(Keyboard.CODE_CAPSLOCK, null, 0, 0);
+ }
+
+ private void onDoubleTapShiftKey(PointerTracker tracker) {
+ // When shift key is double tapped, the first tap is correctly processed as usual tap. And
+ // the second tap is treated as this double tap event, so that we need not mark tracker
+ // calling setAlreadyProcessed() nor remove the tracker from mPointerQueueueue.
+ mKeyboardActionListener.onKey(Keyboard.CODE_CAPSLOCK, null, 0, 0);
+ }
+
private View inflateMiniKeyboardContainer(Key popupKey) {
- int popupKeyboardId = popupKey.popupResId;
+ int popupKeyboardId = popupKey.mPopupResId;
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View container = inflater.inflate(mPopupLayout, null);
if (container == null)
throw new NullPointerException();
- LatinKeyboardBaseView miniKeyboard =
- (LatinKeyboardBaseView)container.findViewById(R.id.LatinKeyboardBaseView);
- miniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {
+ 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);
}
@@ -1110,8 +1151,8 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
miniKeyboard.mGestureDetector = null;
Keyboard keyboard;
- if (popupKey.popupCharacters != null) {
- keyboard = new Keyboard(getContext(), popupKeyboardId, popupKey.popupCharacters,
+ if (popupKey.mPopupCharacters != null) {
+ keyboard = new Keyboard(getContext(), popupKeyboardId, popupKey.mPopupCharacters,
-1, getPaddingLeft() + getPaddingRight());
} else {
keyboard = new Keyboard(getContext(), popupKeyboardId);
@@ -1127,14 +1168,15 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
private static boolean isOneRowKeys(List<Key> keys) {
if (keys.size() == 0) return false;
- final int edgeFlags = keys.get(0).edgeFlags;
+ final int edgeFlags = keys.get(0).mEdgeFlags;
// 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;
+ return (edgeFlags & Keyboard.EDGE_TOP) != 0
+ && (edgeFlags & Keyboard.EDGE_BOTTOM) != 0;
}
/**
@@ -1148,7 +1190,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
// TODO if popupKey.popupCharacters has only one letter, send it as key without opening
// mini keyboard.
- if (popupKey.popupResId == 0)
+ if (popupKey.mPopupResId == 0)
return false;
View container = mMiniKeyboardCache.get(popupKey);
@@ -1156,7 +1198,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
container = inflateMiniKeyboardContainer(popupKey);
mMiniKeyboardCache.put(popupKey, container);
}
- mMiniKeyboard = (LatinKeyboardBaseView)container.findViewById(R.id.LatinKeyboardBaseView);
+ mMiniKeyboard = (KeyboardView)container.findViewById(R.id.KeyboardView);
if (mWindowOffset == null) {
mWindowOffset = new int[2];
getLocationInWindow(mWindowOffset);
@@ -1170,22 +1212,22 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
// 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<Key> miniKeys = mMiniKeyboard.getKeyboard().getKeys();
- final int miniKeyWidth = miniKeys.size() > 0 ? miniKeys.get(0).width : 0;
+ final int miniKeyWidth = miniKeys.size() > 0 ? miniKeys.get(0).mWidth : 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];
+ int popupX = popupKey.mX + mWindowOffset[0];
popupX += getPaddingLeft();
if (isNumberAtLeftmost) {
- popupX += popupKey.width - miniKeyWidth; // adjustment for a) described above
+ popupX += popupKey.mWidth - 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];
+ int popupY = popupKey.mY + mWindowOffset[1];
popupY += getPaddingTop();
popupY -= container.getMeasuredHeight();
popupY += container.getPaddingBottom();
@@ -1201,7 +1243,11 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
mMiniKeyboardOriginX = adjustedX + container.getPaddingLeft() - mWindowOffset[0];
mMiniKeyboardOriginY = y + container.getPaddingTop() - mWindowOffset[1];
mMiniKeyboard.setPopupOffset(adjustedX, y);
- mMiniKeyboard.setShifted(isShifted());
+ 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);
@@ -1212,8 +1258,8 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
// 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);
+ MotionEvent downEvent = generateMiniKeyboardMotionEvent(MotionEvent.ACTION_DOWN, popupKey.mX
+ + popupKey.mWidth / 2, popupKey.mY + popupKey.mHeight / 2, eventTime);
mMiniKeyboard.onTouchEvent(downEvent);
downEvent.recycle();
@@ -1222,45 +1268,15 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
}
private static boolean hasMultiplePopupChars(Key key) {
- if (key.popupCharacters != null && key.popupCharacters.length() > 1) {
- return true;
- }
- return false;
- }
-
- private boolean shouldDrawIconFully(Key key) {
- return isNumberAtEdgeOfPopupChars(key) || isLatinF1Key(key)
- || LatinKeyboard.hasPuncOrSmileysPopup(key);
- }
-
- private boolean shouldDrawLabelAndIcon(Key key) {
- return isNumberAtEdgeOfPopupChars(key) || isNonMicLatinF1Key(key)
- || LatinKeyboard.hasPuncOrSmileysPopup(key);
- }
-
- private boolean isLatinF1Key(Key key) {
- return (mKeyboard instanceof LatinKeyboard) && ((LatinKeyboard)mKeyboard).isF1Key(key);
- }
-
- private boolean isNonMicLatinF1Key(Key key) {
- return isLatinF1Key(key) && key.label != null;
- }
-
- private static boolean isNumberAtEdgeOfPopupChars(Key key) {
- return isNumberAtLeftmostPopupChar(key) || isNumberAtRightmostPopupChar(key);
- }
-
- /* package */ static boolean isNumberAtLeftmostPopupChar(Key key) {
- if (key.popupCharacters != null && key.popupCharacters.length() > 0
- && isAsciiDigit(key.popupCharacters.charAt(0))) {
+ if (key.mPopupCharacters != null && key.mPopupCharacters.length() > 1) {
return true;
}
return false;
}
- /* package */ static boolean isNumberAtRightmostPopupChar(Key key) {
- if (key.popupCharacters != null && key.popupCharacters.length() > 0
- && isAsciiDigit(key.popupCharacters.charAt(key.popupCharacters.length() - 1))) {
+ private static boolean isNumberAtLeftmostPopupChar(Key key) {
+ if (key.mPopupCharacters != null && key.mPopupCharacters.length() > 0
+ && isAsciiDigit(key.mPopupCharacters.charAt(0))) {
return true;
}
return false;
@@ -1278,14 +1294,14 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
private PointerTracker getPointerTracker(final int id) {
final ArrayList<PointerTracker> pointers = mPointerTrackers;
final Key[] keys = mKeys;
- final OnKeyboardActionListener listener = mKeyboardActionListener;
+ 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(keys, mKeyHysteresisDistance);
+ tracker.setKeyboard(mKeyboard, keys, mKeyHysteresisDistance);
if (listener != null)
tracker.setOnKeyboardActionListener(listener);
pointers.add(tracker);
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..e612b52b5
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
@@ -0,0 +1,431 @@
+/*
+ * 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.R;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+
+import android.content.Context;
+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.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import java.util.List;
+import java.util.Locale;
+
+// TODO: We should remove this class
+public class LatinKeyboard extends Keyboard {
+
+ private static final boolean DEBUG_PREFERRED_LETTER = false;
+ private static final String TAG = "LatinKeyboard";
+
+ public static final int OPACITY_FULLY_OPAQUE = 255;
+ private static final int SPACE_LED_LENGTH_PERCENT = 80;
+
+ private Drawable mShiftLockPreviewIcon;
+ private Drawable mSpaceAutoCompletionIndicator;
+ private final Drawable mButtonArrowLeftIcon;
+ private final Drawable mButtonArrowRightIcon;
+ private final int mSpaceBarTextShadowColor;
+ 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 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(CODE_SPACE);
+ }
+
+ /**
+ * @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.setIcon(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.setIcon(new BitmapDrawable(res,
+ drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCompletion)));
+ } else {
+ mSpaceKey.setIcon(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.mWidth;
+ 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.mWidth,
+ (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.setPreviewIcon(mSlidingLocaleIcon);
+ }
+ mSlidingLocaleIcon.setDiff(diff);
+ if (Math.abs(diff) == Integer.MAX_VALUE) {
+ mSpaceKey.setPreviewIcon(mSpacePreviewIcon);
+ } else {
+ mSpaceKey.setPreviewIcon(mSlidingLocaleIcon);
+ }
+ mSpaceKey.getPreviewIcon().invalidateSelf();
+ }
+
+ public int getLanguageChangeDirection() {
+ if (mSpaceKey == null || SubtypeSwitcher.getInstance().getEnabledKeyboardLocaleCount() <= 1
+ || Math.abs(mSpaceDragLastDiff) < mSpaceKey.mWidth * SPACEBAR_DRAG_THRESHOLD) {
+ return 0; // No change
+ }
+ return mSpaceDragLastDiff > 0 ? 1 : -1;
+ }
+
+ 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.
+ */
+ @Override
+ @SuppressWarnings("unused") // SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCHER is constant
+ public boolean isInside(Key key, int x, int y) {
+ final int code = key.mCodes[0];
+ if (code == CODE_SHIFT || code == CODE_DELETE) {
+ y -= key.mHeight / 10;
+ if (code == CODE_SHIFT) x += key.mWidth / 6;
+ if (code == CODE_DELETE) x -= key.mWidth / 6;
+ } else if (code == CODE_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 isOnSpace = key.isOnKey(x, y);
+ if (isOnSpace) {
+ mCurrentlyInSpace = true;
+ mSpaceDragStartX = x;
+ updateLocaleDrag(0);
+ }
+ return isOnSpace;
+ }
+ }
+ } 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.isOnKey(x, y)) {
+ Log.d(TAG, "CORRECTED !!!!!!");
+ }
+ }
+ return mPrefLetter == code;
+ } else {
+ final boolean isOnKey = key.isOnKey(x, y);
+ int[] nearby = getNearestKeys(x, y);
+ List<Key> nearbyKeys = getKeys();
+ if (isOnKey) {
+ // 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.mCodes[0], pref)) {
+ final int dist = distanceFrom(k, x, y);
+ if (dist < (int) (k.mWidth * OVERLAP_PERCENTAGE_LOW_PROB) &&
+ (pref[k.mCodes[0]] > pref[mPrefLetter] * 3)) {
+ mPrefLetter = k.mCodes[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.mCodes[0], pref)) {
+ final int dist = distanceFrom(k, x, y);
+ if (dist < (int) (k.mWidth * OVERLAP_PERCENTAGE_HIGH_PROB)
+ && dist < mPrefDistance) {
+ mPrefLetter = k.mCodes[0];
+ mPrefLetterX = x;
+ mPrefLetterY = y;
+ mPrefDistance = dist;
+ }
+ }
+ }
+ // Didn't find any
+ if (mPrefLetter == 0) {
+ return isOnKey;
+ } else {
+ return mPrefLetter == code;
+ }
+ }
+ }
+
+ // Lock into the spacebar
+ if (mCurrentlyInSpace) return false;
+
+ return key.isOnKey(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.mY && y < k.mY + k.mHeight) {
+ return Math.abs(k.mX + k.mWidth / 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<Key> keys = getKeys();
+ int count = keys.size();
+ for (int i = 0; i < count; i++) {
+ if (keys.get(i).mCodes[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/latin/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 22d39f7aa..5c1c62b05 100644
--- a/java/src/com/android/inputmethod/latin/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -14,13 +14,14 @@
* the License.
*/
-package com.android.inputmethod.latin;
+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.inputmethodservice.Keyboard;
-import android.inputmethodservice.Keyboard.Key;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
@@ -30,16 +31,8 @@ import android.view.MotionEvent;
import java.util.List;
-public class LatinKeyboardView extends LatinKeyboardBaseView {
-
- static final int KEYCODE_OPTIONS = -100;
- static final int KEYCODE_OPTIONS_LONGPRESS = -101;
- static final int KEYCODE_VOICE = -102;
- static final int KEYCODE_F1 = -103;
- static final int KEYCODE_NEXT_LANGUAGE = -104;
- static final int KEYCODE_PREV_LANGUAGE = -105;
-
- private Keyboard mPhoneKeyboard;
+// TODO: We should remove this class
+public class LatinKeyboardView extends KeyboardView {
/** Whether we've started dropping move events because we found a big jump */
private boolean mDroppingEvents;
@@ -61,22 +54,19 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
super(context, attrs, defStyle);
}
- public void setPhoneKeyboard(Keyboard phoneKeyboard) {
- mPhoneKeyboard = phoneKeyboard;
- }
-
@Override
public void setPreviewEnabled(boolean previewEnabled) {
- if (getKeyboard() == mPhoneKeyboard) {
- // Phone keyboard never shows popup preview (except language switch).
+ 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);
}
}
- @Override
- public void setKeyboard(Keyboard k) {
+ public void setLatinKeyboard(LatinKeyboard k) {
super.setKeyboard(k);
// One-seventh of the keyboard width seems like a reasonable threshold
mJumpThresholdSquare = k.getMinWidth() / 7;
@@ -86,12 +76,21 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
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 == KEYCODE_OPTIONS) {
- return invokeOnKey(KEYCODE_OPTIONS_LONGPRESS);
- } else if (primaryCode == '0' && getKeyboard() == mPhoneKeyboard) {
+ int primaryCode = key.mCodes[0];
+ if (primaryCode == Keyboard.CODE_SETTINGS) {
+ return invokeOnKey(Keyboard.CODE_SETTINGS_LONGPRESS);
+ } else if (primaryCode == '0' && getLatinKeyboard().isPhoneKeyboard()) {
// Long pressing on 0 in phone number keypad gives you a '+'.
return invokeOnKey('+');
} else {
@@ -101,17 +100,16 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
private boolean invokeOnKey(int primaryCode) {
getOnKeyboardActionListener().onKey(primaryCode, null,
- LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE,
- LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE);
+ KeyboardView.NOT_A_TOUCH_COORDINATE,
+ KeyboardView.NOT_A_TOUCH_COORDINATE);
return true;
}
@Override
protected CharSequence adjustCase(CharSequence label) {
- Keyboard keyboard = getKeyboard();
- if (keyboard.isShifted()
- && keyboard instanceof LatinKeyboard
- && ((LatinKeyboard) keyboard).isAlphaKeyboard()
+ LatinKeyboard keyboard = getLatinKeyboard();
+ if (keyboard.isAlphaKeyboard()
+ && keyboard.isShiftedOrShiftLocked()
&& !TextUtils.isEmpty(label) && label.length() < 3
&& Character.isLowerCase(label.charAt(0))) {
label = label.toString().toUpperCase();
@@ -119,16 +117,6 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
return label;
}
- public boolean setShiftLocked(boolean shiftLocked) {
- Keyboard keyboard = getKeyboard();
- if (keyboard instanceof LatinKeyboard) {
- ((LatinKeyboard)keyboard).setShiftLocked(shiftLocked);
- invalidateAllKeys();
- return true;
- }
- return false;
- }
-
/**
* 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.
@@ -208,7 +196,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
@Override
public boolean onTouchEvent(MotionEvent me) {
- LatinKeyboard keyboard = (LatinKeyboard) getKeyboard();
+ LatinKeyboard keyboard = getLatinKeyboard();
if (DEBUG_LINE) {
mLastX = (int) me.getX();
mLastY = (int) me.getY();
@@ -228,7 +216,8 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
int languageDirection = keyboard.getLanguageChangeDirection();
if (languageDirection != 0) {
getOnKeyboardActionListener().onKey(
- languageDirection == 1 ? KEYCODE_NEXT_LANGUAGE : KEYCODE_PREV_LANGUAGE,
+ languageDirection == 1
+ ? Keyboard.CODE_NEXT_LANGUAGE : Keyboard.CODE_PREV_LANGUAGE,
null, mLastX, mLastY);
me.setAction(MotionEvent.ACTION_CANCEL);
keyboard.keyReleased();
@@ -241,8 +230,8 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
/**************************** INSTRUMENTATION *******************************/
- static final boolean DEBUG_AUTO_PLAY = false;
- static final boolean DEBUG_LINE = false;
+ 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;
@@ -257,7 +246,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
private int mLastY;
private Paint mPaint;
- private void setKeyboardLocal(Keyboard k) {
+ private void setKeyboardLocal(LatinKeyboard k) {
if (DEBUG_AUTO_PLAY) {
findKeys();
if (mHandler2 == null) {
@@ -283,8 +272,8 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
}
c = mStringToPlay.charAt(mStringIndex);
}
- int x = mAsciiKeys[c].x + 10;
- int y = mAsciiKeys[c].y + 26;
+ int x = mAsciiKeys[c].mX + 10;
+ int y = mAsciiKeys[c].mY + 26;
MotionEvent me = MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(),
MotionEvent.ACTION_DOWN, x, y, 0);
@@ -296,8 +285,8 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
break;
case MSG_TOUCH_UP:
char cUp = mStringToPlay.charAt(mStringIndex);
- int x2 = mAsciiKeys[cUp].x + 10;
- int y2 = mAsciiKeys[cUp].y + 26;
+ int x2 = mAsciiKeys[cUp].mX + 10;
+ int y2 = mAsciiKeys[cUp].mY + 26;
mStringIndex++;
MotionEvent me2 = MotionEvent.obtain(SystemClock.uptimeMillis(),
@@ -318,10 +307,10 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
}
private void findKeys() {
- List<Key> keys = getKeyboard().getKeys();
+ List<Key> keys = getLatinKeyboard().getKeys();
// Get the keys on this keyboard
for (int i = 0; i < keys.size(); i++) {
- int code = keys.get(i).codes[0];
+ int code = keys.get(i).mCodes[0];
if (code >= 0 && code <= 255) {
mAsciiKeys[code] = keys.get(i);
}
@@ -372,4 +361,10 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
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/keyboard/MiniKeyboardKeyDetector.java
index 356e62d48..774b39ab7 100644
--- a/java/src/com/android/inputmethod/latin/MiniKeyboardKeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
@@ -14,11 +14,9 @@
* the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.keyboard;
-import android.inputmethodservice.Keyboard.Key;
-
-class MiniKeyboardKeyDetector extends KeyDetector {
+public class MiniKeyboardKeyDetector extends KeyDetector {
private static final int MAX_NEARBY_KEYS = 1;
private final int mSlideAllowanceSquare;
@@ -41,19 +39,20 @@ class MiniKeyboardKeyDetector extends KeyDetector {
final Key[] keys = getKeys();
final int touchX = getTouchX(x);
final int touchY = getTouchY(y);
- int closestKeyIndex = LatinKeyboardBaseView.NOT_A_KEY;
+
+ int closestKeyIndex = NOT_A_KEY;
int closestKeyDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
final int keyCount = keys.length;
- for (int i = 0; i < keyCount; i++) {
- final Key key = keys[i];
- int dist = key.squaredDistanceFrom(touchX, touchY);
+ for (int index = 0; index < keyCount; index++) {
+ final int dist = keys[index].squaredDistanceToEdge(touchX, touchY);
if (dist < closestKeyDist) {
- closestKeyIndex = i;
+ closestKeyIndex = index;
closestKeyDist = dist;
}
}
- if (allKeys != null && closestKeyIndex != LatinKeyboardBaseView.NOT_A_KEY)
- allKeys[0] = keys[closestKeyIndex].codes[0];
+
+ if (allKeys != null && closestKeyIndex != NOT_A_KEY)
+ allKeys[0] = keys[closestKeyIndex].mCodes[0];
return closestKeyIndex;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/ModifierKeyState.java b/java/src/com/android/inputmethod/keyboard/ModifierKeyState.java
new file mode 100644
index 000000000..1bd3d8040
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/ModifierKeyState.java
@@ -0,0 +1,79 @@
+/*
+ * 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.util.Log;
+
+public class ModifierKeyState {
+ protected static final String TAG = "ModifierKeyState";
+ protected static final boolean DEBUG = KeyboardSwitcher.DEBUG_STATE;
+
+ protected static final int RELEASING = 0;
+ protected static final int PRESSING = 1;
+ protected static final int MOMENTARY = 2;
+
+ protected final String mName;
+ protected int mState = RELEASING;
+
+ public ModifierKeyState(String name) {
+ mName = name;
+ }
+
+ public void onPress() {
+ final int oldState = mState;
+ mState = PRESSING;
+ if (DEBUG)
+ Log.d(TAG, mName + ".onPress: " + toString(oldState) + " > " + this);
+ }
+
+ public void onRelease() {
+ final int oldState = mState;
+ mState = RELEASING;
+ if (DEBUG)
+ Log.d(TAG, mName + ".onRelease: " + toString(oldState) + " > " + this);
+ }
+
+ public void onOtherKeyPressed() {
+ final int oldState = mState;
+ if (oldState == PRESSING)
+ mState = MOMENTARY;
+ if (DEBUG)
+ Log.d(TAG, mName + ".onOtherKeyPressed: " + toString(oldState) + " > " + this);
+ }
+
+ public boolean isReleasing() {
+ return mState == RELEASING;
+ }
+
+ public boolean isMomentary() {
+ return mState == MOMENTARY;
+ }
+
+ @Override
+ public String toString() {
+ return toString(mState);
+ }
+
+ protected String toString(int state) {
+ switch (state) {
+ case RELEASING: return "RELEASING";
+ case PRESSING: return "PRESSING";
+ case MOMENTARY: return "MOMENTARY";
+ default: return "UNKNOWN";
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 90218e4ab..8570491f8 100644
--- a/java/src/com/android/inputmethod/latin/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -14,14 +14,12 @@
* the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.keyboard;
-import com.android.inputmethod.latin.LatinKeyboardBaseView.OnKeyboardActionListener;
-import com.android.inputmethod.latin.LatinKeyboardBaseView.UIHandler;
+import com.android.inputmethod.keyboard.KeyboardView.UIHandler;
+import com.android.inputmethod.latin.R;
import android.content.res.Resources;
-import android.inputmethodservice.Keyboard;
-import android.inputmethodservice.Keyboard.Key;
import android.util.Log;
import android.view.MotionEvent;
@@ -41,18 +39,20 @@ public class PointerTracker {
// 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 = LatinKeyboardBaseView.NOT_A_KEY;
- private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
+ private static final int NOT_A_KEY = KeyDetector.NOT_A_KEY;
+ private static final int[] KEY_DELETE = { Keyboard.CODE_DELETE };
private final UIProxy mProxy;
private final UIHandler mHandler;
private final KeyDetector mKeyDetector;
- private OnKeyboardActionListener mListener;
+ private KeyboardActionListener mListener;
private final boolean mHasDistinctMultitouch;
+ private Keyboard mKeyboard;
private Key[] mKeys;
private int mKeyHysteresisDistanceSquared = -1;
@@ -175,17 +175,19 @@ public class PointerTracker {
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) {
+ public void setOnKeyboardActionListener(KeyboardActionListener listener) {
mListener = listener;
}
- public void setKeyboard(Key[] keys, float keyHysteresisDistance) {
- if (keys == null || keyHysteresisDistance < 0)
+ 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.
@@ -204,9 +206,9 @@ public class PointerTracker {
Key key = getKey(keyIndex);
if (key == null)
return false;
- int primaryCode = key.codes[0];
- return primaryCode == Keyboard.KEYCODE_SHIFT
- || primaryCode == Keyboard.KEYCODE_MODE_CHANGE;
+ int primaryCode = key.mCodes[0];
+ return primaryCode == Keyboard.CODE_SHIFT
+ || primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL;
}
public boolean isModifier() {
@@ -217,14 +219,21 @@ public class PointerTracker {
return isModifierInternal(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null));
}
+ public boolean isOnShiftKey(int x, int y) {
+ final Key key = getKey(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null));
+ return key != null && key.mCodes[0] == Keyboard.CODE_SHIFT;
+ }
+
public boolean isSpaceKey(int keyIndex) {
Key key = getKey(keyIndex);
- return key != null && key.codes[0] == LatinIME.KEYCODE_SPACE;
+ return key != null && key.mCodes[0] == Keyboard.CODE_SPACE;
}
- public void updateKey(int keyIndex) {
- if (mKeyAlreadyProcessed)
- return;
+ public void releaseKey() {
+ updateKeyGraphics(NOT_A_KEY);
+ }
+
+ private void updateKeyGraphics(int keyIndex) {
int oldKeyIndex = mPreviousKey;
mPreviousKey = keyIndex;
if (keyIndex != oldKeyIndex) {
@@ -273,21 +282,21 @@ public class PointerTracker {
checkMultiTap(eventTime, keyIndex);
if (mListener != null) {
if (isValidKeyIndex(keyIndex)) {
- mListener.onPress(mKeys[keyIndex].codes[0]);
+ mListener.onPress(mKeys[keyIndex].mCodes[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) {
+ if (mKeys[keyIndex].mRepeatable) {
repeatKey(keyIndex);
mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this);
mIsRepeatableKey = true;
}
- mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this);
+ startLongPressTimer(keyIndex);
}
- showKeyPreviewAndUpdateKey(keyIndex);
+ showKeyPreviewAndUpdateKeyGraphics(keyIndex);
}
public void onMoveEvent(int x, int y, long eventTime) {
@@ -301,32 +310,33 @@ public class PointerTracker {
if (isValidKeyIndex(keyIndex)) {
if (oldKey == null) {
keyState.onMoveToNewKey(keyIndex, x, y);
- mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this);
+ startLongPressTimer(keyIndex);
} else if (!isMinorMoveBounce(x, y, keyIndex)) {
if (mListener != null)
- mListener.onRelease(oldKey.codes[0]);
+ mListener.onRelease(oldKey.mCodes[0]);
resetMultiTap();
keyState.onMoveToNewKey(keyIndex, x, y);
- mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this);
+ startLongPressTimer(keyIndex);
}
} else {
if (oldKey != null) {
if (mListener != null)
- mListener.onRelease(oldKey.codes[0]);
+ mListener.onRelease(oldKey.mCodes[0]);
keyState.onMoveToNewKey(keyIndex, x ,y);
- mHandler.cancelLongPressTimer();
+ mHandler.cancelLongPressTimers();
} else if (!isMinorMoveBounce(x, y, keyIndex)) {
resetMultiTap();
keyState.onMoveToNewKey(keyIndex, x ,y);
- mHandler.cancelLongPressTimer();
+ mHandler.cancelLongPressTimers();
}
}
- showKeyPreviewAndUpdateKey(mKeyState.getKeyIndex());
+ 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();
@@ -338,7 +348,6 @@ public class PointerTracker {
x = mKeyState.getKeyX();
y = mKeyState.getKeyY();
}
- showKeyPreviewAndUpdateKey(NOT_A_KEY);
if (!mIsRepeatableKey) {
detectAndSendKey(keyIndex, x, y, eventTime);
}
@@ -352,7 +361,7 @@ public class PointerTracker {
debugLog("onCancelEvt:", x, y);
mHandler.cancelKeyTimers();
mHandler.cancelPopupPreview();
- showKeyPreviewAndUpdateKey(NOT_A_KEY);
+ showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
int keyIndex = mKeyState.getKeyIndex();
if (isValidKeyIndex(keyIndex))
mProxy.invalidateKey(mKeys[keyIndex]);
@@ -363,7 +372,7 @@ public class PointerTracker {
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);
+ detectAndSendKey(keyIndex, key.mX, key.mY, -1);
}
}
@@ -395,26 +404,14 @@ public class PointerTracker {
if (newKey == curKey) {
return true;
} else if (isValidKeyIndex(curKey)) {
- return getSquareDistanceToKeyEdge(x, y, mKeys[curKey]) < mKeyHysteresisDistanceSquared;
+ return mKeys[curKey].squaredDistanceToEdge(x, y) < mKeyHysteresisDistanceSquared;
} else {
return false;
}
}
- private static int getSquareDistanceToKeyEdge(int x, int y, Key key) {
- final int left = key.x;
- final int right = key.x + key.width;
- final int top = key.y;
- final int bottom = key.y + key.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;
- }
-
- private void showKeyPreviewAndUpdateKey(int keyIndex) {
- updateKey(keyIndex);
+ 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.
@@ -425,33 +422,51 @@ public class PointerTracker {
}
}
+ private void startLongPressTimer(int keyIndex) {
+ Key key = getKey(keyIndex);
+ if (key.mCodes[0] == Keyboard.CODE_SHIFT) {
+ mHandler.startLongPressShiftTimer(mLongPressShiftKeyTimeout, keyIndex, this);
+ } else {
+ mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this);
+ }
+ }
+
private void detectAndSendKey(int index, int x, int y, long eventTime) {
- final OnKeyboardActionListener listener = mListener;
+ final KeyboardActionListener listener = mListener;
final Key key = getKey(index);
if (key == null) {
if (listener != null)
listener.onCancel();
} else {
- if (key.text != null) {
+ if (key.mOutputText != null) {
if (listener != null) {
- listener.onText(key.text);
+ listener.onText(key.mOutputText);
listener.onRelease(NOT_A_KEY);
}
} else {
- int code = key.codes[0];
+ int code = key.mCodes[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);
+ mListener.onKey(Keyboard.CODE_DELETE, KEY_DELETE, x, y);
} else {
mTapCount = 0;
}
- code = key.codes[mTapCount];
+ code = key.mCodes[mTapCount];
}
+
+ // If keyboard is in manual temporary upper case state and key has manual temporary
+ // shift code, alternate character code should be sent.
+ if (mKeyboard.isManualTemporaryUpperCase()
+ && key.mManualTemporaryUpperCaseCode != 0) {
+ code = key.mManualTemporaryUpperCaseCode;
+ 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
@@ -478,10 +493,10 @@ public class PointerTracker {
if (mInMultiTap) {
// Multi-tap
mPreviewLabel.setLength(0);
- mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
+ mPreviewLabel.append((char) key.mCodes[mTapCount < 0 ? 0 : mTapCount]);
return mPreviewLabel;
} else {
- return key.label;
+ return key.mLabel;
}
}
@@ -499,10 +514,10 @@ public class PointerTracker {
final boolean isMultiTap =
(eventTime < mLastTapTime + mMultiTapKeyTimeout && keyIndex == mLastSentIndex);
- if (key.codes.length > 1) {
+ if (key.mCodes.length > 1) {
mInMultiTap = true;
if (isMultiTap) {
- mTapCount = (mTapCount + 1) % key.codes.length;
+ mTapCount = (mTapCount + 1) % key.mCodes.length;
return;
} else {
mTapCount = -1;
@@ -521,7 +536,7 @@ public class PointerTracker {
if (key == null) {
code = "----";
} else {
- int primaryCode = key.codes[0];
+ int primaryCode = key.mCodes[0];
code = String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode);
}
Log.d(TAG, String.format("%s%s[%d] %3d,%3d %3d(%s) %s", title,
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..e559b4cde
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java
@@ -0,0 +1,79 @@
+/*
+ * 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<PointerTracker> mQueue = new LinkedList<PointerTracker>();
+
+ public void add(PointerTracker tracker) {
+ mQueue.add(tracker);
+ }
+
+ public int lastIndexOf(PointerTracker tracker) {
+ LinkedList<PointerTracker> 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<PointerTracker> 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);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("[");
+ for (PointerTracker tracker : mQueue) {
+ if (sb.length() > 1)
+ sb.append(" ");
+ sb.append(String.format("%d", tracker.mPointerId));
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java b/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java
index 325ce674c..43596ae2e 100644
--- a/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java
@@ -14,13 +14,11 @@
* the License.
*/
-package com.android.inputmethod.latin;
-
-import android.inputmethodservice.Keyboard.Key;
+package com.android.inputmethod.keyboard;
import java.util.Arrays;
-class ProximityKeyDetector extends KeyDetector {
+public class ProximityKeyDetector extends KeyDetector {
private static final int MAX_NEARBY_KEYS = 12;
// working area
@@ -36,51 +34,42 @@ class ProximityKeyDetector extends KeyDetector {
final Key[] keys = getKeys();
final int touchX = getTouchX(x);
final int touchY = getTouchY(y);
- int primaryIndex = LatinKeyboardBaseView.NOT_A_KEY;
- int closestKey = LatinKeyboardBaseView.NOT_A_KEY;
+
+ int primaryIndex = NOT_A_KEY;
+ int closestKeyIndex = NOT_A_KEY;
int closestKeyDist = mProximityThresholdSquare + 1;
- int[] distances = mDistances;
+ final int[] distances = mDistances;
Arrays.fill(distances, Integer.MAX_VALUE);
- int [] nearestKeyIndices = mKeyboard.getNearestKeys(touchX, touchY);
- final int keyCount = nearestKeyIndices.length;
- for (int i = 0; i < keyCount; i++) {
- final Key key = keys[nearestKeyIndices[i]];
- int dist = 0;
- boolean isInside = key.isInside(touchX, touchY);
- if (isInside) {
- primaryIndex = nearestKeyIndices[i];
- }
-
- if (((mProximityCorrectOn
- && (dist = key.squaredDistanceFrom(touchX, touchY)) < mProximityThresholdSquare)
- || isInside)
- && key.codes[0] > 32) {
- // Find insertion point
- final int nCodes = key.codes.length;
+ 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;
- closestKey = nearestKeyIndices[i];
+ closestKeyIndex = index;
}
if (allKeys == null) continue;
-
+ final int nCodes = key.mCodes.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);
+ distances.length - (j + nCodes));
System.arraycopy(allKeys, j, allKeys, j + nCodes,
- allKeys.length - j - nCodes);
- System.arraycopy(key.codes, 0, allKeys, j, nCodes);
+ allKeys.length - (j + nCodes));
+ System.arraycopy(key.mCodes, 0, allKeys, j, nCodes);
Arrays.fill(distances, j, j + nCodes, dist);
break;
}
}
}
}
- if (primaryIndex == LatinKeyboardBaseView.NOT_A_KEY) {
- primaryIndex = closestKey;
- }
- return primaryIndex;
+
+ 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..37fa4e39e
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/Row.java
@@ -0,0 +1,81 @@
+/*
+ * 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 final int mDefaultWidth;
+ /** Default height of a key in this row. */
+ public final int mDefaultHeight;
+ /** Default horizontal gap between keys in this row. */
+ public final int mDefaultHorizontalGap;
+ /** Vertical gap following this row. */
+ public final int mVerticalGap;
+ /**
+ * 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 final int mRowEdgeFlags;
+
+ private final Keyboard mKeyboard;
+
+ public Row(Keyboard keyboard) {
+ this.mKeyboard = keyboard;
+ mDefaultHeight = keyboard.getKeyHeight();
+ mDefaultWidth = keyboard.getKeyWidth();
+ mDefaultHorizontalGap = keyboard.getHorizontalGap();
+ mVerticalGap = keyboard.getVerticalGap();
+ mRowEdgeFlags = Keyboard.EDGE_TOP | Keyboard.EDGE_BOTTOM;
+ }
+
+ public Row(Resources res, Keyboard keyboard, XmlResourceParser parser) {
+ this.mKeyboard = keyboard;
+ final int keyboardWidth = keyboard.getKeyboardWidth();
+ final int keyboardHeight = keyboard.getKeyboardHeight();
+ TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard);
+ mDefaultWidth = KeyboardParser.getDimensionOrFraction(a,
+ R.styleable.Keyboard_keyWidth, keyboardWidth, keyboard.getKeyWidth());
+ mDefaultHeight = KeyboardParser.getDimensionOrFraction(a,
+ R.styleable.Keyboard_keyHeight, keyboardHeight, keyboard.getKeyHeight());
+ mDefaultHorizontalGap = KeyboardParser.getDimensionOrFraction(a,
+ R.styleable.Keyboard_horizontalGap, keyboardWidth, keyboard.getHorizontalGap());
+ mVerticalGap = KeyboardParser.getDimensionOrFraction(a,
+ R.styleable.Keyboard_verticalGap, keyboardHeight, keyboard.getVerticalGap());
+ a.recycle();
+ a = res.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard_Row);
+ mRowEdgeFlags = a.getInt(R.styleable.Keyboard_Row_rowEdgeFlags, 0);
+ a.recycle();
+ }
+
+ public Keyboard getKeyboard() {
+ return mKeyboard;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/ShiftKeyState.java b/java/src/com/android/inputmethod/keyboard/ShiftKeyState.java
new file mode 100644
index 000000000..9229208a9
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/ShiftKeyState.java
@@ -0,0 +1,69 @@
+/*
+ * 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.util.Log;
+
+public class ShiftKeyState extends ModifierKeyState {
+ private static final int PRESSING_ON_SHIFTED = 3; // both temporary shifted & shift locked
+ private static final int IGNORING = 4;
+
+ public ShiftKeyState(String name) {
+ super(name);
+ }
+
+ @Override
+ public void onOtherKeyPressed() {
+ int oldState = mState;
+ if (oldState == PRESSING) {
+ mState = MOMENTARY;
+ } else if (oldState == PRESSING_ON_SHIFTED) {
+ mState = IGNORING;
+ }
+ if (DEBUG)
+ Log.d(TAG, mName + ".onOtherKeyPressed: " + toString(oldState) + " > " + this);
+ }
+
+ public void onPressOnShifted() {
+ int oldState = mState;
+ mState = PRESSING_ON_SHIFTED;
+ if (DEBUG)
+ Log.d(TAG, mName + ".onPressOnShifted: " + toString(oldState) + " > " + this);
+ }
+
+ public boolean isPressingOnShifted() {
+ return mState == PRESSING_ON_SHIFTED;
+ }
+
+ public boolean isIgnoring() {
+ return mState == IGNORING;
+ }
+
+ @Override
+ public String toString() {
+ return toString(mState);
+ }
+
+ @Override
+ protected String toString(int state) {
+ switch (state) {
+ case PRESSING_ON_SHIFTED: return "PRESSING_ON_SHIFTED";
+ case IGNORING: return "IGNORING";
+ default: return super.toString(state);
+ }
+ }
+}
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/latin/SwipeTracker.java b/java/src/com/android/inputmethod/keyboard/SwipeTracker.java
index 970e91965..730cdc390 100644
--- a/java/src/com/android/inputmethod/latin/SwipeTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/SwipeTracker.java
@@ -14,11 +14,11 @@
* the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.keyboard;
import android.view.MotionEvent;
-class SwipeTracker {
+public class SwipeTracker {
private static final int NUM_PAST = 4;
private static final int LONGEST_PAST_TIME = 200;
@@ -91,7 +91,7 @@ class SwipeTracker {
return mYVelocity;
}
- static class EventRingBuffer {
+ public static class EventRingBuffer {
private final int bufSize;
private final float xBuf[];
private final float yBuf[];
@@ -154,4 +154,4 @@ class SwipeTracker {
end = advance(end);
}
}
-} \ No newline at end of file
+}
diff --git a/java/src/com/android/inputmethod/latin/AutoDictionary.java b/java/src/com/android/inputmethod/latin/AutoDictionary.java
index 4fbb5b012..a3bf25642 100644
--- a/java/src/com/android/inputmethod/latin/AutoDictionary.java
+++ b/java/src/com/android/inputmethod/latin/AutoDictionary.java
@@ -16,10 +16,6 @@
package com.android.inputmethod.latin;
-import java.util.HashMap;
-import java.util.Set;
-import java.util.Map.Entry;
-
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -30,6 +26,10 @@ import android.os.AsyncTask;
import android.provider.BaseColumns;
import android.util.Log;
+import java.util.HashMap;
+import java.util.Map.Entry;
+import java.util.Set;
+
/**
* Stores new words temporarily until they are promoted to the user dictionary
* for longevity. Words in the auto dictionary are used to determine if it's ok
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index d0e143dd0..c7f629f15 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -16,16 +16,16 @@
package com.android.inputmethod.latin;
-import java.io.InputStream;
+import android.content.Context;
+import android.util.Log;
+
import java.io.IOException;
+import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.Channels;
import java.util.Arrays;
-import android.content.Context;
-import android.util.Log;
-
/**
* Implements a static, compacted, binary dictionary of standard words.
*/
@@ -45,16 +45,15 @@ public class BinaryDictionary extends Dictionary {
private static final int MAX_BIGRAMS = 60;
private static final int TYPED_LETTER_MULTIPLIER = 2;
- private static final boolean ENABLE_MISSED_CHARACTERS = true;
private int mDicTypeId;
private int mNativeDict;
private int mDictLength;
- private int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_ALTERNATIVES];
- private char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS];
- private char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS];
- private int[] mFrequencies = new int[MAX_WORDS];
- private int[] mFrequencies_bigrams = new int[MAX_BIGRAMS];
+ private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_ALTERNATIVES];
+ private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS];
+ private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS];
+ private final int[] mFrequencies = new int[MAX_WORDS];
+ private final int[] mFrequencies_bigrams = new int[MAX_BIGRAMS];
// Keep a reference to the native dict direct buffer in Java to avoid
// unexpected deallocation of the direct buffer.
private ByteBuffer mNativeDictDirectBuffer;
@@ -95,18 +94,19 @@ public class BinaryDictionary extends Dictionary {
}
mDictLength = byteBuffer.capacity();
mNativeDict = openNative(mNativeDictDirectBuffer,
- TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER);
+ TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER,
+ MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES);
}
mDicTypeId = dicTypeId;
}
private native int openNative(ByteBuffer bb, int typedLetterMultiplier,
- int fullWordMultiplier);
+ int fullWordMultiplier, int maxWordLength, int maxWords, int maxAlternatives);
private native void closeNative(int dict);
private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
private native int getSuggestionsNative(int dict, int[] inputCodes, int codesSize,
- char[] outputChars, int[] frequencies, int maxWordLength, int maxWords,
- int maxAlternatives, int skipPos, int[] nextLettersFrequencies, int nextLettersSize);
+ char[] outputChars, int[] frequencies,
+ int[] nextLettersFrequencies, int nextLettersSize);
private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength,
int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies,
int maxWordLength, int maxBigrams, int maxAlternatives);
@@ -132,7 +132,8 @@ public class BinaryDictionary extends Dictionary {
Log.e(TAG, "Read " + got + " bytes, expected " + total);
} else {
mNativeDict = openNative(mNativeDictDirectBuffer,
- TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER);
+ TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER,
+ MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES);
mDictLength = total;
}
} catch (IOException e) {
@@ -189,7 +190,7 @@ public class BinaryDictionary extends Dictionary {
final int codesSize = codes.size();
// Won't deal with really long words.
if (codesSize > MAX_WORD_LENGTH - 1) return;
-
+
Arrays.fill(mInputCodes, -1);
for (int i = 0; i < codesSize; i++) {
int[] alternatives = codes.getCodesAt(i);
@@ -199,27 +200,10 @@ public class BinaryDictionary extends Dictionary {
Arrays.fill(mOutputChars, (char) 0);
Arrays.fill(mFrequencies, 0);
- int count = getSuggestionsNative(mNativeDict, mInputCodes, codesSize,
- mOutputChars, mFrequencies,
- MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES, -1,
- nextLettersFrequencies,
+ int count = getSuggestionsNative(mNativeDict, mInputCodes, codesSize, mOutputChars,
+ mFrequencies, nextLettersFrequencies,
nextLettersFrequencies != null ? nextLettersFrequencies.length : 0);
- // If there aren't sufficient suggestions, search for words by allowing wild cards at
- // the different character positions. This feature is not ready for prime-time as we need
- // to figure out the best ranking for such words compared to proximity corrections and
- // completions.
- if (ENABLE_MISSED_CHARACTERS && count < 5) {
- for (int skip = 0; skip < codesSize; skip++) {
- int tempCount = getSuggestionsNative(mNativeDict, mInputCodes, codesSize,
- mOutputChars, mFrequencies,
- MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES, skip,
- null, 0);
- count = Math.max(count, tempCount);
- if (tempCount > 0) break;
- }
- }
-
for (int j = 0; j < count; j++) {
if (mFrequencies[j] < 1) break;
int start = j * MAX_WORD_LENGTH;
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index 68f288925..68f288925 100755..100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index e954c0818..2a7767ddd 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -16,11 +16,11 @@
package com.android.inputmethod.latin;
-import java.util.LinkedList;
-
import android.content.Context;
import android.os.AsyncTask;
+import java.util.LinkedList;
+
/**
* Base class for an in-memory dictionary that can grow dynamically and can
* be searched for suggestions and valid words.
diff --git a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
index e811a2cdd..ad3f1db9b 100644
--- a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
+++ b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
@@ -16,11 +16,6 @@
package com.android.inputmethod.latin;
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Locale;
-
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Configuration;
@@ -32,12 +27,18 @@ import android.preference.PreferenceGroup;
import android.preference.PreferenceManager;
import android.text.TextUtils;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Locale;
+
public class InputLanguageSelection extends PreferenceActivity {
+ private SharedPreferences mPrefs;
private String mSelectedLanguages;
private ArrayList<Loc> mAvailableLanguages = new ArrayList<Loc>();
private static final String[] BLACKLIST_LANGUAGES = {
- "ko", "ja", "zh", "el"
+ "ko", "ja", "zh", "el", "zz"
};
private static class Loc implements Comparable<Object> {
@@ -56,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);
}
@@ -66,15 +68,15 @@ public class InputLanguageSelection extends PreferenceActivity {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.language_prefs);
// Get the settings preferences
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
- mSelectedLanguages = sp.getString(LatinIME.PREF_SELECTED_LANGUAGES, "");
+ mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+ mSelectedLanguages = mPrefs.getString(LatinIME.PREF_SELECTED_LANGUAGES, "");
String[] languageList = mSelectedLanguages.split(",");
mAvailableLanguages = getUniqueLocales();
PreferenceGroup parent = getPreferenceScreen();
for (int i = 0; i < mAvailableLanguages.size(); i++) {
CheckBoxPreference pref = new CheckBoxPreference(this);
Locale locale = mAvailableLanguages.get(i).locale;
- pref.setTitle(LanguageSwitcher.toTitleCase(locale.getDisplayName(locale)));
+ pref.setTitle(SubtypeSwitcher.getFullDisplayName(locale, true));
boolean checked = isLocaleIn(locale, languageList);
pref.setChecked(checked);
if (hasDictionary(locale)) {
@@ -140,8 +142,7 @@ public class InputLanguageSelection extends PreferenceActivity {
}
}
if (checkedLanguages.length() < 1) checkedLanguages = null; // Save null
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
- Editor editor = sp.edit();
+ Editor editor = mPrefs.edit();
editor.putString(LatinIME.PREF_SELECTED_LANGUAGES, checkedLanguages);
SharedPreferencesCompat.apply(editor);
}
@@ -167,7 +168,7 @@ public class InputLanguageSelection extends PreferenceActivity {
if (finalSize == 0) {
preprocess[finalSize++] =
- new Loc(LanguageSwitcher.toTitleCase(l.getDisplayName(l)), l);
+ new Loc(SubtypeSwitcher.getFullDisplayName(l, true), l);
} else {
// check previous entry:
// same lang and a country -> upgrade to full name and
@@ -175,15 +176,15 @@ public class InputLanguageSelection extends PreferenceActivity {
// diff lang -> insert ours with lang-only name
if (preprocess[finalSize-1].locale.getLanguage().equals(
language)) {
- preprocess[finalSize-1].label = LanguageSwitcher.toTitleCase(
- preprocess[finalSize-1].locale.getDisplayName());
+ preprocess[finalSize-1].label = SubtypeSwitcher.getFullDisplayName(
+ preprocess[finalSize-1].locale, false);
preprocess[finalSize++] =
- new Loc(LanguageSwitcher.toTitleCase(l.getDisplayName()), l);
+ new Loc(SubtypeSwitcher.getFullDisplayName(l, false), l);
} else {
String displayName;
if (s.equals("zz_ZZ")) {
} else {
- displayName = LanguageSwitcher.toTitleCase(l.getDisplayName(l));
+ displayName = SubtypeSwitcher.getFullDisplayName(l, true);
preprocess[finalSize++] = new Loc(displayName, l);
}
}
diff --git a/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java b/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java
deleted file mode 100644
index a7b695eb3..000000000
--- a/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java
+++ /dev/null
@@ -1,538 +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.SharedPreferences;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.preference.PreferenceManager;
-import android.view.InflateException;
-
-import java.lang.ref.SoftReference;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Locale;
-
-public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceChangeListener {
-
- public static final int MODE_NONE = 0;
- public static final int MODE_TEXT = 1;
- public static final int MODE_SYMBOLS = 2;
- public static final int MODE_PHONE = 3;
- public static final int MODE_URL = 4;
- public static final int MODE_EMAIL = 5;
- public static final int MODE_IM = 6;
- public static final int MODE_WEB = 7;
-
- // Main keyboard layouts without the settings key
- public static final int KEYBOARDMODE_NORMAL = R.id.mode_normal;
- public static final int KEYBOARDMODE_URL = R.id.mode_url;
- public static final int KEYBOARDMODE_EMAIL = R.id.mode_email;
- public static final int KEYBOARDMODE_IM = R.id.mode_im;
- public static final int KEYBOARDMODE_WEB = R.id.mode_webentry;
- // Main keyboard layouts with the settings key
- public static final int KEYBOARDMODE_NORMAL_WITH_SETTINGS_KEY =
- R.id.mode_normal_with_settings_key;
- public static final int KEYBOARDMODE_URL_WITH_SETTINGS_KEY =
- R.id.mode_url_with_settings_key;
- public static final int KEYBOARDMODE_EMAIL_WITH_SETTINGS_KEY =
- R.id.mode_email_with_settings_key;
- public static final int KEYBOARDMODE_IM_WITH_SETTINGS_KEY =
- R.id.mode_im_with_settings_key;
- public static final int KEYBOARDMODE_WEB_WITH_SETTINGS_KEY =
- R.id.mode_webentry_with_settings_key;
-
- // Symbols keyboard layout without the settings key
- public static final int KEYBOARDMODE_SYMBOLS = R.id.mode_symbols;
- // Symbols keyboard layout with the settings key
- public static final int KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY =
- R.id.mode_symbols_with_settings_key;
-
- public static final String DEFAULT_LAYOUT_ID = "4";
- public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20100902";
- private static final int[] THEMES = new int [] {
- R.layout.input_basic, R.layout.input_basic_highcontrast, R.layout.input_stone_normal,
- R.layout.input_stone_bold, R.layout.input_gingerbread};
-
- // Ids for each characters' color in the keyboard
- private static final int CHAR_THEME_COLOR_WHITE = 0;
- private static final int CHAR_THEME_COLOR_BLACK = 1;
-
- // Tables which contains resource ids for each character theme color
- private static final int[] KBD_PHONE = new int[] {R.xml.kbd_phone, R.xml.kbd_phone_black};
- private static final int[] KBD_PHONE_SYMBOLS = new int[] {
- R.xml.kbd_phone_symbols, R.xml.kbd_phone_symbols_black};
- private static final int[] KBD_SYMBOLS = new int[] {
- R.xml.kbd_symbols, R.xml.kbd_symbols_black};
- private static final int[] KBD_SYMBOLS_SHIFT = new int[] {
- R.xml.kbd_symbols_shift, R.xml.kbd_symbols_shift_black};
- private static final int[] KBD_QWERTY = new int[] {R.xml.kbd_qwerty, R.xml.kbd_qwerty_black};
-
- private static final int SYMBOLS_MODE_STATE_NONE = 0;
- private static final int SYMBOLS_MODE_STATE_BEGIN = 1;
- private static final int SYMBOLS_MODE_STATE_SYMBOL = 2;
-
- private LatinKeyboardView mInputView;
- private static final int[] ALPHABET_MODES = {
- KEYBOARDMODE_NORMAL,
- KEYBOARDMODE_URL,
- KEYBOARDMODE_EMAIL,
- KEYBOARDMODE_IM,
- KEYBOARDMODE_WEB,
- KEYBOARDMODE_NORMAL_WITH_SETTINGS_KEY,
- KEYBOARDMODE_URL_WITH_SETTINGS_KEY,
- KEYBOARDMODE_EMAIL_WITH_SETTINGS_KEY,
- KEYBOARDMODE_IM_WITH_SETTINGS_KEY,
- KEYBOARDMODE_WEB_WITH_SETTINGS_KEY };
-
- private final LatinIME mInputMethodService;
-
- private KeyboardId mSymbolsId;
- private KeyboardId mSymbolsShiftedId;
-
- private KeyboardId mCurrentId;
- private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboards;
-
- private int mMode = MODE_NONE; /** One of the MODE_XXX values */
- private int mImeOptions;
- private boolean mIsSymbols;
- /** mIsAutoCompletionActive indicates that auto completed word will be input instead of
- * what user actually typed. */
- private boolean mIsAutoCompletionActive;
- private boolean mHasVoice;
- private boolean mVoiceOnPrimary;
- private boolean mPreferSymbols;
- private int mSymbolsModeState = SYMBOLS_MODE_STATE_NONE;
-
- // Indicates whether or not we have the settings key
- private boolean mHasSettingsKey;
- private static final int SETTINGS_KEY_MODE_AUTO = R.string.settings_key_mode_auto;
- private static final int SETTINGS_KEY_MODE_ALWAYS_SHOW = R.string.settings_key_mode_always_show;
- // NOTE: No need to have SETTINGS_KEY_MODE_ALWAYS_HIDE here because it's not being referred to
- // in the source code now.
- // Default is SETTINGS_KEY_MODE_AUTO.
- private static final int DEFAULT_SETTINGS_KEY_MODE = SETTINGS_KEY_MODE_AUTO;
-
- private int mLastDisplayWidth;
- private LanguageSwitcher mLanguageSwitcher;
- private Locale mInputLocale;
-
- private int mLayoutId;
-
- public KeyboardSwitcher(LatinIME ims) {
- mInputMethodService = ims;
-
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ims);
- mLayoutId = Integer.valueOf(prefs.getString(PREF_KEYBOARD_LAYOUT, DEFAULT_LAYOUT_ID));
- updateSettingsKeyState(prefs);
- prefs.registerOnSharedPreferenceChangeListener(this);
-
- mKeyboards = new HashMap<KeyboardId, SoftReference<LatinKeyboard>>();
- mSymbolsId = makeSymbolsId(false);
- mSymbolsShiftedId = makeSymbolsShiftedId(false);
- }
-
- /**
- * Sets the input locale, when there are multiple locales for input.
- * If no locale switching is required, then the locale should be set to null.
- * @param locale the current input locale, or null for default locale with no locale
- * button.
- */
- public void setLanguageSwitcher(LanguageSwitcher languageSwitcher) {
- mLanguageSwitcher = languageSwitcher;
- mInputLocale = mLanguageSwitcher.getInputLocale();
- }
-
- private KeyboardId makeSymbolsId(boolean hasVoice) {
- return new KeyboardId(KBD_SYMBOLS[getCharColorId()], mHasSettingsKey ?
- KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY : KEYBOARDMODE_SYMBOLS,
- false, hasVoice);
- }
-
- private KeyboardId makeSymbolsShiftedId(boolean hasVoice) {
- return new KeyboardId(KBD_SYMBOLS_SHIFT[getCharColorId()], mHasSettingsKey ?
- KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY : KEYBOARDMODE_SYMBOLS,
- false, hasVoice);
- }
-
- public void makeKeyboards(boolean forceCreate) {
- mSymbolsId = makeSymbolsId(mHasVoice && !mVoiceOnPrimary);
- mSymbolsShiftedId = makeSymbolsShiftedId(mHasVoice && !mVoiceOnPrimary);
-
- if (forceCreate) mKeyboards.clear();
- // Configuration change is coming after the keyboard gets recreated. So don't rely on that.
- // If keyboards have already been made, check if we have a screen width change and
- // create the keyboard layouts again at the correct orientation
- int displayWidth = mInputMethodService.getMaxWidth();
- if (displayWidth == mLastDisplayWidth) return;
- mLastDisplayWidth = displayWidth;
- if (!forceCreate) mKeyboards.clear();
- }
-
- /**
- * Represents the parameters necessary to construct a new LatinKeyboard,
- * which also serve as a unique identifier for each keyboard type.
- */
- private static class KeyboardId {
- // TODO: should have locale and portrait/landscape orientation?
- public final int mXml;
- public final int mKeyboardMode; /** A KEYBOARDMODE_XXX value */
- public final boolean mEnableShiftLock;
- public final boolean mHasVoice;
-
- private final int mHashCode;
-
- public KeyboardId(int xml, int mode, boolean enableShiftLock, boolean hasVoice) {
- this.mXml = xml;
- this.mKeyboardMode = mode;
- this.mEnableShiftLock = enableShiftLock;
- this.mHasVoice = hasVoice;
-
- this.mHashCode = Arrays.hashCode(new Object[] {
- xml, mode, enableShiftLock, hasVoice
- });
- }
-
- public KeyboardId(int xml, boolean hasVoice) {
- this(xml, 0, false, hasVoice);
- }
-
- @Override
- public boolean equals(Object other) {
- return other instanceof KeyboardId && equals((KeyboardId) other);
- }
-
- private boolean equals(KeyboardId other) {
- return other.mXml == this.mXml
- && other.mKeyboardMode == this.mKeyboardMode
- && other.mEnableShiftLock == this.mEnableShiftLock
- && other.mHasVoice == this.mHasVoice;
- }
-
- @Override
- public int hashCode() {
- return mHashCode;
- }
- }
-
- public void setVoiceMode(boolean enableVoice, boolean voiceOnPrimary) {
- if (enableVoice != mHasVoice || voiceOnPrimary != mVoiceOnPrimary) {
- mKeyboards.clear();
- }
- mHasVoice = enableVoice;
- mVoiceOnPrimary = voiceOnPrimary;
- setKeyboardMode(mMode, mImeOptions, mHasVoice, mIsSymbols);
- }
-
- private boolean hasVoiceButton(boolean isSymbols) {
- return mHasVoice && (isSymbols != mVoiceOnPrimary);
- }
-
- public void setKeyboardMode(int mode, int imeOptions, boolean enableVoice) {
- mSymbolsModeState = SYMBOLS_MODE_STATE_NONE;
- mPreferSymbols = mode == MODE_SYMBOLS;
- if (mode == MODE_SYMBOLS) {
- mode = MODE_TEXT;
- }
- try {
- setKeyboardMode(mode, imeOptions, enableVoice, mPreferSymbols);
- } catch (RuntimeException e) {
- LatinImeLogger.logOnException(mode + "," + imeOptions + "," + mPreferSymbols, e);
- }
- }
-
- private void setKeyboardMode(int mode, int imeOptions, boolean enableVoice, boolean isSymbols) {
- if (mInputView == null) return;
- mMode = mode;
- mImeOptions = imeOptions;
- if (enableVoice != mHasVoice) {
- // TODO clean up this unnecessary recursive call.
- setVoiceMode(enableVoice, mVoiceOnPrimary);
- }
- mIsSymbols = isSymbols;
-
- mInputView.setPreviewEnabled(mInputMethodService.getPopupOn());
- KeyboardId id = getKeyboardId(mode, imeOptions, isSymbols);
- LatinKeyboard keyboard = null;
- keyboard = getKeyboard(id);
-
- if (mode == MODE_PHONE) {
- mInputView.setPhoneKeyboard(keyboard);
- }
-
- mCurrentId = id;
- mInputView.setKeyboard(keyboard);
- keyboard.setShifted(false);
- keyboard.setShiftLocked(keyboard.isShiftLocked());
- keyboard.setImeOptions(mInputMethodService.getResources(), mMode, imeOptions);
- keyboard.setColorOfSymbolIcons(mIsAutoCompletionActive, isBlackSym());
- // Update the settings key state because number of enabled IMEs could have been changed
- updateSettingsKeyState(PreferenceManager.getDefaultSharedPreferences(mInputMethodService));
- }
-
- private LatinKeyboard getKeyboard(KeyboardId id) {
- SoftReference<LatinKeyboard> ref = mKeyboards.get(id);
- LatinKeyboard keyboard = (ref == null) ? null : ref.get();
- if (keyboard == null) {
- Resources orig = mInputMethodService.getResources();
- Configuration conf = orig.getConfiguration();
- Locale saveLocale = conf.locale;
- conf.locale = mInputLocale;
- orig.updateConfiguration(conf, null);
- keyboard = new LatinKeyboard(mInputMethodService, id.mXml, id.mKeyboardMode);
- keyboard.setVoiceMode(hasVoiceButton(id.mXml == R.xml.kbd_symbols
- || id.mXml == R.xml.kbd_symbols_black), mHasVoice);
- keyboard.setLanguageSwitcher(mLanguageSwitcher, mIsAutoCompletionActive, isBlackSym());
-
- if (id.mEnableShiftLock) {
- keyboard.enableShiftLock();
- }
- mKeyboards.put(id, new SoftReference<LatinKeyboard>(keyboard));
-
- conf.locale = saveLocale;
- orig.updateConfiguration(conf, null);
- }
- return keyboard;
- }
-
- private KeyboardId getKeyboardId(int mode, int imeOptions, boolean isSymbols) {
- boolean hasVoice = hasVoiceButton(isSymbols);
- int charColorId = getCharColorId();
- // TODO: generalize for any KeyboardId
- int keyboardRowsResId = KBD_QWERTY[charColorId];
- if (isSymbols) {
- if (mode == MODE_PHONE) {
- return new KeyboardId(KBD_PHONE_SYMBOLS[charColorId], hasVoice);
- } else {
- return new KeyboardId(KBD_SYMBOLS[charColorId], mHasSettingsKey ?
- KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY : KEYBOARDMODE_SYMBOLS,
- false, hasVoice);
- }
- }
- switch (mode) {
- case MODE_NONE:
- LatinImeLogger.logOnWarning(
- "getKeyboardId:" + mode + "," + imeOptions + "," + isSymbols);
- /* fall through */
- case MODE_TEXT:
- return new KeyboardId(keyboardRowsResId, mHasSettingsKey ?
- KEYBOARDMODE_NORMAL_WITH_SETTINGS_KEY : KEYBOARDMODE_NORMAL,
- true, hasVoice);
- case MODE_SYMBOLS:
- return new KeyboardId(KBD_SYMBOLS[charColorId], mHasSettingsKey ?
- KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY : KEYBOARDMODE_SYMBOLS,
- false, hasVoice);
- case MODE_PHONE:
- return new KeyboardId(KBD_PHONE[charColorId], hasVoice);
- case MODE_URL:
- return new KeyboardId(keyboardRowsResId, mHasSettingsKey ?
- KEYBOARDMODE_URL_WITH_SETTINGS_KEY : KEYBOARDMODE_URL, true, hasVoice);
- case MODE_EMAIL:
- return new KeyboardId(keyboardRowsResId, mHasSettingsKey ?
- KEYBOARDMODE_EMAIL_WITH_SETTINGS_KEY : KEYBOARDMODE_EMAIL, true, hasVoice);
- case MODE_IM:
- return new KeyboardId(keyboardRowsResId, mHasSettingsKey ?
- KEYBOARDMODE_IM_WITH_SETTINGS_KEY : KEYBOARDMODE_IM, true, hasVoice);
- case MODE_WEB:
- return new KeyboardId(keyboardRowsResId, mHasSettingsKey ?
- KEYBOARDMODE_WEB_WITH_SETTINGS_KEY : KEYBOARDMODE_WEB, true, hasVoice);
- }
- return null;
- }
-
- public int getKeyboardMode() {
- return mMode;
- }
-
- public boolean isAlphabetMode() {
- if (mCurrentId == null) {
- return false;
- }
- int currentMode = mCurrentId.mKeyboardMode;
- for (Integer mode : ALPHABET_MODES) {
- if (currentMode == mode) {
- return true;
- }
- }
- return false;
- }
-
- public void setShifted(boolean shifted) {
- if (mInputView != null) {
- mInputView.setShifted(shifted);
- }
- }
-
- public void setShiftLocked(boolean shiftLocked) {
- if (mInputView != null) {
- mInputView.setShiftLocked(shiftLocked);
- }
- }
-
- public void toggleShift() {
- if (isAlphabetMode())
- return;
- if (mCurrentId.equals(mSymbolsId) || !mCurrentId.equals(mSymbolsShiftedId)) {
- LatinKeyboard symbolsShiftedKeyboard = getKeyboard(mSymbolsShiftedId);
- mCurrentId = mSymbolsShiftedId;
- mInputView.setKeyboard(symbolsShiftedKeyboard);
- // Symbol shifted keyboard has an ALT key that has a caps lock style indicator. To
- // enable the indicator, we need to call enableShiftLock() and setShiftLocked(true).
- // Thus we can keep the ALT key's Key.on value true while LatinKey.onRelease() is
- // called.
- symbolsShiftedKeyboard.enableShiftLock();
- symbolsShiftedKeyboard.setShiftLocked(true);
- symbolsShiftedKeyboard.setImeOptions(mInputMethodService.getResources(),
- mMode, mImeOptions);
- } else {
- LatinKeyboard symbolsKeyboard = getKeyboard(mSymbolsId);
- mCurrentId = mSymbolsId;
- mInputView.setKeyboard(symbolsKeyboard);
- // Symbol keyboard has an ALT key that has a caps lock style indicator. To disable the
- // indicator, we need to call enableShiftLock() and setShiftLocked(false).
- symbolsKeyboard.enableShiftLock();
- symbolsKeyboard.setShifted(false);
- symbolsKeyboard.setImeOptions(mInputMethodService.getResources(), mMode, mImeOptions);
- }
- }
-
- public void toggleSymbols() {
- setKeyboardMode(mMode, mImeOptions, mHasVoice, !mIsSymbols);
- if (mIsSymbols && !mPreferSymbols) {
- mSymbolsModeState = SYMBOLS_MODE_STATE_BEGIN;
- } else {
- mSymbolsModeState = SYMBOLS_MODE_STATE_NONE;
- }
- }
-
- public boolean hasDistinctMultitouch() {
- return mInputView != null && mInputView.hasDistinctMultitouch();
- }
-
- /**
- * Updates state machine to figure out when to automatically switch back to alpha mode.
- * Returns true if the keyboard needs to switch back
- */
- public boolean onKey(int key) {
- // Switch back to alpha mode if user types one or more non-space/enter characters
- // followed by a space/enter
- switch (mSymbolsModeState) {
- case SYMBOLS_MODE_STATE_BEGIN:
- if (key != LatinIME.KEYCODE_SPACE && key != LatinIME.KEYCODE_ENTER && key > 0) {
- mSymbolsModeState = SYMBOLS_MODE_STATE_SYMBOL;
- }
- break;
- case SYMBOLS_MODE_STATE_SYMBOL:
- if (key == LatinIME.KEYCODE_ENTER || key == LatinIME.KEYCODE_SPACE) return true;
- break;
- }
- return false;
- }
-
- public LatinKeyboardView getInputView() {
- return mInputView;
- }
-
- public void recreateInputView() {
- changeLatinKeyboardView(mLayoutId, true);
- }
-
- private void changeLatinKeyboardView(int newLayout, boolean forceReset) {
- if (mLayoutId != newLayout || mInputView == null || forceReset) {
- if (mInputView != null) {
- mInputView.closing();
- }
- if (THEMES.length <= newLayout) {
- newLayout = Integer.valueOf(DEFAULT_LAYOUT_ID);
- }
-
- LatinIMEUtil.GCUtils.getInstance().reset();
- boolean tryGC = true;
- for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
- try {
- mInputView = (LatinKeyboardView) mInputMethodService.getLayoutInflater(
- ).inflate(THEMES[newLayout], null);
- tryGC = false;
- } catch (OutOfMemoryError e) {
- tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(
- mLayoutId + "," + newLayout, e);
- } catch (InflateException e) {
- tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(
- mLayoutId + "," + newLayout, e);
- }
- }
- mInputView.setOnKeyboardActionListener(mInputMethodService);
- mLayoutId = newLayout;
- }
- mInputMethodService.mHandler.post(new Runnable() {
- public void run() {
- if (mInputView != null) {
- mInputMethodService.setInputView(mInputView);
- }
- mInputMethodService.updateInputViewShown();
- }});
- }
-
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (PREF_KEYBOARD_LAYOUT.equals(key)) {
- changeLatinKeyboardView(
- Integer.valueOf(sharedPreferences.getString(key, DEFAULT_LAYOUT_ID)), false);
- } else if (LatinIMESettings.PREF_SETTINGS_KEY.equals(key)) {
- updateSettingsKeyState(sharedPreferences);
- recreateInputView();
- }
- }
-
- public boolean isBlackSym () {
- if (mInputView != null && mInputView.getSymbolColorScheme() == 1) {
- return true;
- }
- return false;
- }
-
- private int getCharColorId () {
- if (isBlackSym()) {
- return CHAR_THEME_COLOR_BLACK;
- } else {
- return CHAR_THEME_COLOR_WHITE;
- }
- }
-
- public void onAutoCompletionStateChanged(boolean isAutoCompletion) {
- if (isAutoCompletion != mIsAutoCompletionActive) {
- LatinKeyboardView keyboardView = getInputView();
- mIsAutoCompletionActive = isAutoCompletion;
- keyboardView.invalidateKey(((LatinKeyboard) keyboardView.getKeyboard())
- .onAutoCompletionStateChanged(isAutoCompletion));
- }
- }
-
- private void updateSettingsKeyState(SharedPreferences prefs) {
- Resources resources = mInputMethodService.getResources();
- final String settingsKeyMode = prefs.getString(LatinIMESettings.PREF_SETTINGS_KEY,
- resources.getString(DEFAULT_SETTINGS_KEY_MODE));
- // We show the settings key when 1) SETTINGS_KEY_MODE_ALWAYS_SHOW or
- // 2) SETTINGS_KEY_MODE_AUTO and there are two or more enabled IMEs on the system
- if (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_ALWAYS_SHOW))
- || (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_AUTO))
- && LatinIMEUtil.hasMultipleEnabledIMEs(mInputMethodService))) {
- mHasSettingsKey = true;
- } else {
- mHasSettingsKey = false;
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/LanguageSwitcher.java b/java/src/com/android/inputmethod/latin/LanguageSwitcher.java
index 7b5c30491..fc725bf1b 100644
--- a/java/src/com/android/inputmethod/latin/LanguageSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/LanguageSwitcher.java
@@ -16,21 +16,21 @@
package com.android.inputmethod.latin;
-import java.util.Locale;
-
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
-import android.preference.PreferenceManager;
import android.text.TextUtils;
+import java.util.ArrayList;
+import java.util.Locale;
+
/**
* Keeps track of list of selected input languages and the current
* input language that the user has selected.
*/
public class LanguageSwitcher {
- private Locale[] mLocales;
- private LatinIME mIme;
+ private final ArrayList<Locale> mLocales = new ArrayList<Locale>();
+ private final LatinIME mIme;
private String[] mSelectedLanguageArray;
private String mSelectedLanguages;
private int mCurrentIndex = 0;
@@ -40,15 +40,10 @@ public class LanguageSwitcher {
public LanguageSwitcher(LatinIME ime) {
mIme = ime;
- mLocales = new Locale[0];
- }
-
- public Locale[] getLocales() {
- return mLocales;
}
public int getLocaleCount() {
- return mLocales.length;
+ return mLocales.size();
}
/**
@@ -61,10 +56,10 @@ public class LanguageSwitcher {
String currentLanguage = sp.getString(LatinIME.PREF_INPUT_LANGUAGE, null);
if (selectedLanguages == null || selectedLanguages.length() < 1) {
loadDefaults();
- if (mLocales.length == 0) {
+ if (mLocales.size() == 0) {
return false;
}
- mLocales = new Locale[0];
+ mLocales.clear();
return true;
}
if (selectedLanguages.equals(mSelectedLanguages)) {
@@ -77,7 +72,7 @@ public class LanguageSwitcher {
if (currentLanguage != null) {
// Find the index
mCurrentIndex = 0;
- for (int i = 0; i < mLocales.length; i++) {
+ for (int i = 0; i < mLocales.size(); i++) {
if (mSelectedLanguageArray[i].equals(currentLanguage)) {
mCurrentIndex = i;
break;
@@ -96,11 +91,11 @@ public class LanguageSwitcher {
}
private void constructLocales() {
- mLocales = new Locale[mSelectedLanguageArray.length];
- for (int i = 0; i < mLocales.length; i++) {
- final String lang = mSelectedLanguageArray[i];
- mLocales[i] = new Locale(lang.substring(0, 2),
+ mLocales.clear();
+ for (final String lang : mSelectedLanguageArray) {
+ final Locale locale = new Locale(lang.substring(0, 2),
lang.length() > 4 ? lang.substring(3, 5) : "");
+ mLocales.add(locale);
}
}
@@ -129,7 +124,17 @@ public class LanguageSwitcher {
public Locale getInputLocale() {
if (getLocaleCount() == 0) return mDefaultInputLocale;
- return mLocales[mCurrentIndex];
+ return mLocales.get(mCurrentIndex);
+ }
+
+ private int nextLocaleIndex() {
+ final int size = mLocales.size();
+ return (mCurrentIndex + 1) % size;
+ }
+
+ private int prevLocaleIndex() {
+ final int size = mLocales.size();
+ return (mCurrentIndex - 1 + size) % size;
}
/**
@@ -139,8 +144,7 @@ public class LanguageSwitcher {
*/
public Locale getNextInputLocale() {
if (getLocaleCount() == 0) return mDefaultInputLocale;
-
- return mLocales[(mCurrentIndex + 1) % mLocales.length];
+ return mLocales.get(nextLocaleIndex());
}
/**
@@ -166,8 +170,7 @@ public class LanguageSwitcher {
*/
public Locale getPrevInputLocale() {
if (getLocaleCount() == 0) return mDefaultInputLocale;
-
- return mLocales[(mCurrentIndex - 1 + mLocales.length) % mLocales.length];
+ return mLocales.get(prevLocaleIndex());
}
public void reset() {
@@ -175,27 +178,16 @@ public class LanguageSwitcher {
}
public void next() {
- mCurrentIndex++;
- if (mCurrentIndex >= mLocales.length) mCurrentIndex = 0; // Wrap around
+ mCurrentIndex = nextLocaleIndex();
}
public void prev() {
- mCurrentIndex--;
- if (mCurrentIndex < 0) mCurrentIndex = mLocales.length - 1; // Wrap around
+ mCurrentIndex = prevLocaleIndex();
}
- public void persist() {
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mIme);
- Editor editor = sp.edit();
+ public void persist(SharedPreferences prefs) {
+ Editor editor = prefs.edit();
editor.putString(LatinIME.PREF_INPUT_LANGUAGE, getInputLanguage());
SharedPreferencesCompat.apply(editor);
}
-
- static String toTitleCase(String s) {
- if (s.length() == 0) {
- return s;
- }
-
- return Character.toUpperCase(s.charAt(0)) + s.substring(1);
- }
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index b6fee1170..3089153ad 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -16,10 +16,14 @@
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.KeyboardSwitcher;
+import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.keyboard.LatinKeyboardView;
import com.android.inputmethod.latin.LatinIMEUtil.RingCharBuffer;
-import com.android.inputmethod.voice.FieldContext;
-import com.android.inputmethod.voice.SettingsUtil;
-import com.android.inputmethod.voice.VoiceInput;
+import com.android.inputmethod.voice.VoiceIMEConnector;
import org.xmlpull.v1.XmlPullParserException;
@@ -34,16 +38,14 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.inputmethodservice.InputMethodService;
-import android.inputmethodservice.Keyboard;
import android.media.AudioManager;
import android.os.Debug;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
+import android.os.Vibrator;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
-import android.speech.SpeechRecognizer;
-import android.text.ClipboardManager;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -51,7 +53,6 @@ import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
@@ -63,128 +64,93 @@ import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
import android.widget.LinearLayout;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
+import java.util.Arrays;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
/**
* Input method implementation for Qwerty'ish keyboard.
*/
public class LatinIME extends InputMethodService
- implements LatinKeyboardBaseView.OnKeyboardActionListener,
- VoiceInput.UiListener,
- SharedPreferences.OnSharedPreferenceChangeListener {
+ implements KeyboardActionListener,
+ SharedPreferences.OnSharedPreferenceChangeListener,
+ Tutorial.TutorialListener {
private static final String TAG = "LatinIME";
private static final boolean PERF_DEBUG = false;
- static final boolean DEBUG = false;
- static final boolean TRACE = false;
- static final boolean VOICE_INSTALLED = true;
- static final boolean ENABLE_VOICE_BUTTON = true;
+ private static final boolean DEBUG = false;
+ private static final boolean TRACE = false;
- private static final String PREF_VIBRATE_ON = "vibrate_on";
private static final String PREF_SOUND_ON = "sound_on";
private static final String PREF_POPUP_ON = "popup_on";
private static final String PREF_AUTO_CAP = "auto_cap";
private static final String PREF_QUICK_FIXES = "quick_fixes";
- private static final String PREF_SHOW_SUGGESTIONS = "show_suggestions";
- private static final String PREF_AUTO_COMPLETE = "auto_complete";
- //private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
- private static final String PREF_VOICE_MODE = "voice_mode";
-
- // Whether or not the user has used voice input before (and thus, whether to show the
- // first-run warning dialog or not).
- private static final String PREF_HAS_USED_VOICE_INPUT = "has_used_voice_input";
-
- // Whether or not the user has used voice input from an unsupported locale UI before.
- // For example, the user has a Chinese UI but activates voice input.
- private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE =
- "has_used_voice_input_unsupported_locale";
-
- // A list of locales which are supported by default for voice input, unless we get a
- // different list from Gservices.
- public static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES =
- "en " +
- "en_US " +
- "en_GB " +
- "en_AU " +
- "en_CA " +
- "en_IE " +
- "en_IN " +
- "en_NZ " +
- "en_SG " +
- "en_ZA ";
-
- // The private IME option used to indicate that no microphone should be shown for a
- // given text field. For instance this is specified by the search dialog when the
- // dialog is already showing a voice search button.
- private static final String IME_OPTION_NO_MICROPHONE = "nm";
+ private static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
+ private static final String PREF_AUTO_COMPLETION_THRESHOLD = "auto_completion_threshold";
+ private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
public static final String PREF_INPUT_LANGUAGE = "input_language";
private static final String PREF_RECORRECTION_ENABLED = "recorrection_enabled";
- private static final int MSG_UPDATE_SUGGESTIONS = 0;
- private static final int MSG_START_TUTORIAL = 1;
- private static final int MSG_UPDATE_SHIFT_STATE = 2;
- private static final int MSG_VOICE_RESULTS = 3;
- private static final int MSG_UPDATE_OLD_SUGGESTIONS = 4;
+ private static final int DELAY_UPDATE_SUGGESTIONS = 180;
+ private static final int DELAY_UPDATE_OLD_SUGGESTIONS = 300;
+ private static final int DELAY_UPDATE_SHIFT_STATE = 300;
+ private static final int DELAY_START_TUTORIAL = 500;
// How many continuous deletes at which to start deleting at a higher speed.
private static final int DELETE_ACCELERATE_AT = 20;
// Key events coming any faster than this are long-presses.
private static final int QUICK_PRESS = 200;
- static final int KEYCODE_ENTER = '\n';
- static final int KEYCODE_SPACE = ' ';
- static final int KEYCODE_PERIOD = '.';
-
// Contextual menu positions
private static final int POS_METHOD = 0;
private static final int POS_SETTINGS = 1;
- //private LatinKeyboardView mInputView;
+ private int mSuggestionVisibility;
+ private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE
+ = R.string.prefs_suggestion_visibility_show_value;
+ private static final int SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE
+ = R.string.prefs_suggestion_visibility_show_only_portrait_value;
+ private static final int SUGGESTION_VISIBILILTY_HIDE_VALUE
+ = R.string.prefs_suggestion_visibility_hide_value;
+
+ private static final int[] SUGGESTION_VISIBILITY_VALUE_ARRAY = new int[] {
+ SUGGESTION_VISIBILILTY_SHOW_VALUE,
+ SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE,
+ SUGGESTION_VISIBILILTY_HIDE_VALUE
+ };
+
private LinearLayout mCandidateViewContainer;
private CandidateView mCandidateView;
private Suggest mSuggest;
private CompletionInfo[] mCompletions;
private AlertDialog mOptionsDialog;
- private AlertDialog mVoiceWarningDialog;
- /* package */ KeyboardSwitcher mKeyboardSwitcher;
+ private InputMethodManager mImm;
+ private KeyboardSwitcher mKeyboardSwitcher;
+ private SubtypeSwitcher mSubtypeSwitcher;
+ private VoiceIMEConnector mVoiceConnector;
private UserDictionary mUserDictionary;
private UserBigramDictionary mUserBigramDictionary;
private ContactsDictionary mContactsDictionary;
private AutoDictionary mAutoDictionary;
- private Hints mHints;
-
private Resources mResources;
+ private SharedPreferences mPrefs;
- private String mInputLocale;
- private String mSystemLocale;
- private LanguageSwitcher mLanguageSwitcher;
-
- private StringBuilder mComposing = new StringBuilder();
+ private final StringBuilder mComposing = new StringBuilder();
private WordComposer mWord = new WordComposer();
- private int mCommittedLength;
- private boolean mPredicting;
- private boolean mRecognizing;
- private boolean mAfterVoiceInput;
- private boolean mImmediatelyAfterVoiceInput;
- private boolean mShowingVoiceSuggestions;
- private boolean mVoiceInputHighlighted;
- private boolean mEnableVoiceButton;
private CharSequence mBestWord;
+ private boolean mPredicting;
private boolean mPredictionOn;
private boolean mCompletionOn;
private boolean mHasDictionary;
@@ -192,72 +158,49 @@ public class LatinIME extends InputMethodService
private boolean mJustAddedAutoSpace;
private boolean mAutoCorrectEnabled;
private boolean mReCorrectionEnabled;
- // Bigram Suggestion is disabled in this version.
- private final boolean mBigramSuggestionEnabled = false;
+ private boolean mBigramSuggestionEnabled;
private boolean mAutoCorrectOn;
- // TODO move this state variable outside LatinIME
- private boolean mCapsLock;
- private boolean mPasswordText;
private boolean mVibrateOn;
private boolean mSoundOn;
private boolean mPopupOn;
private boolean mAutoCap;
private boolean mQuickFixes;
- private boolean mHasUsedVoiceInput;
- private boolean mHasUsedVoiceInputUnsupportedLocale;
- private boolean mLocaleSupportedForVoiceInput;
- private boolean mShowSuggestions;
- private boolean mIsShowingHint;
- private int mCorrectionMode;
- private boolean mEnableVoice = true;
- private boolean mVoiceOnPrimary;
- private int mOrientation;
- private List<CharSequence> mSuggestPuncList;
+
+ private int mCorrectionMode;
+ private int mCommittedLength;
+ private int mOrientation;
// Keep track of the last selection range to decide if we need to show word alternatives
- private int mLastSelectionStart;
- private int mLastSelectionEnd;
+ private int mLastSelectionStart;
+ private int mLastSelectionEnd;
+ private List<CharSequence> mSuggestPuncList;
// Input type is such that we should not auto-correct
private boolean mInputTypeNoAutoCorrect;
// Indicates whether the suggestion strip is to be on in landscape
private boolean mJustAccepted;
- private CharSequence mJustRevertedSeparator;
+ private boolean mJustReverted;
private int mDeleteCount;
private long mLastKeyTime;
- // Modifier keys state
- private ModifierKeyState mShiftKeyState = new ModifierKeyState();
- private ModifierKeyState mSymbolKeyState = new ModifierKeyState();
-
private Tutorial mTutorial;
private AudioManager mAudioManager;
// Align sound effect volume on music volume
- private final float FX_VOLUME = -1.0f;
+ private static final float FX_VOLUME = -1.0f;
private boolean mSilentMode;
/* package */ String mWordSeparators;
private String mSentenceSeparators;
private String mSuggestPuncs;
- private VoiceInput mVoiceInput;
- private VoiceResults mVoiceResults = new VoiceResults();
+ // TODO: Move this flag to VoiceIMEConnector
private boolean mConfigurationChanging;
// Keeps track of most recently inserted text (multi-character key) for reverting
private CharSequence mEnteredText;
private boolean mRefreshKeyboardRequired;
- // For each word, a list of potential replacements, usually from voice.
- private Map<String, List<CharSequence>> mWordToSuggestions =
- new HashMap<String, List<CharSequence>>();
-
- private ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
-
- private class VoiceResults {
- List<String> candidates;
- Map<String, List<CharSequence>> alternatives;
- }
+ private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
public abstract static class WordAlternatives {
protected CharSequence mChosenWord;
@@ -307,56 +250,101 @@ public class LatinIME extends InputMethodService
}
}
- /* package */ Handler mHandler = new Handler() {
+ public final UIHandler mHandler = new UIHandler();
+
+ public class UIHandler extends Handler {
+ private static final int MSG_UPDATE_SUGGESTIONS = 0;
+ private static final int MSG_UPDATE_OLD_SUGGESTIONS = 1;
+ private static final int MSG_UPDATE_SHIFT_STATE = 2;
+ private static final int MSG_VOICE_RESULTS = 3;
+ private static final int MSG_START_TUTORIAL = 4;
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_UPDATE_SUGGESTIONS:
- updateSuggestions();
- break;
- case MSG_UPDATE_OLD_SUGGESTIONS:
- setOldSuggestions();
- break;
- case MSG_START_TUTORIAL:
- if (mTutorial == null) {
- if (mKeyboardSwitcher.getInputView().isShown()) {
- mTutorial = new Tutorial(
- LatinIME.this, mKeyboardSwitcher.getInputView());
- mTutorial.start();
- } else {
- // Try again soon if the view is not yet showing
- sendMessageDelayed(obtainMessage(MSG_START_TUTORIAL), 100);
- }
+ case MSG_UPDATE_SUGGESTIONS:
+ updateSuggestions();
+ break;
+ case MSG_UPDATE_OLD_SUGGESTIONS:
+ setOldSuggestions();
+ break;
+ case MSG_UPDATE_SHIFT_STATE:
+ mKeyboardSwitcher.updateShiftState();
+ break;
+ case MSG_VOICE_RESULTS:
+ mVoiceConnector.handleVoiceResults(mKeyboardSwitcher, preferCapitalization()
+ || (mKeyboardSwitcher.isAlphabetMode()
+ && mKeyboardSwitcher.isShiftedOrShiftLocked()));
+ break;
+ case MSG_START_TUTORIAL:
+ if (mTutorial == null) {
+ if (mKeyboardSwitcher.isInputViewShown()) {
+ mTutorial = new Tutorial(LatinIME.this, mKeyboardSwitcher);
+ mTutorial.start();
+ } else {
+ // Try again soon if the view is not yet showing
+ sendMessageDelayed(obtainMessage(MSG_START_TUTORIAL), 100);
}
- break;
- case MSG_UPDATE_SHIFT_STATE:
- updateShiftKeyState(getCurrentInputEditorInfo());
- break;
- case MSG_VOICE_RESULTS:
- handleVoiceResults();
- break;
+ }
+ break;
}
}
- };
+
+ public void postUpdateSuggestions() {
+ removeMessages(MSG_UPDATE_SUGGESTIONS);
+ sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTIONS), DELAY_UPDATE_SUGGESTIONS);
+ }
+
+ public void cancelUpdateSuggestions() {
+ removeMessages(MSG_UPDATE_SUGGESTIONS);
+ }
+
+ public boolean hasPendingUpdateSuggestions() {
+ return hasMessages(MSG_UPDATE_SUGGESTIONS);
+ }
+
+ public void postUpdateOldSuggestions() {
+ removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
+ sendMessageDelayed(obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS),
+ DELAY_UPDATE_OLD_SUGGESTIONS);
+ }
+
+ public void cancelUpdateOldSuggestions() {
+ removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
+ }
+
+ public void postUpdateShiftKeyState() {
+ removeMessages(MSG_UPDATE_SHIFT_STATE);
+ sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), DELAY_UPDATE_SHIFT_STATE);
+ }
+
+ public void cancelUpdateShiftState() {
+ removeMessages(MSG_UPDATE_SHIFT_STATE);
+ }
+
+ public void updateVoiceResults() {
+ sendMessage(obtainMessage(MSG_VOICE_RESULTS));
+ }
+
+ public void startTutorial() {
+ sendMessageDelayed(obtainMessage(MSG_START_TUTORIAL), DELAY_START_TUTORIAL);
+ }
+ }
@Override
public void onCreate() {
- LatinImeLogger.init(this);
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ mPrefs = prefs;
+ LatinImeLogger.init(this, prefs);
+ SubtypeSwitcher.init(this, prefs);
+ KeyboardSwitcher.init(this, prefs);
super.onCreate();
//setStatusIcon(R.drawable.ime_qwerty);
mResources = getResources();
+ mImm = ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE));
final Configuration conf = mResources.getConfiguration();
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- mLanguageSwitcher = new LanguageSwitcher(this);
- mLanguageSwitcher.loadLocales(prefs);
- mKeyboardSwitcher = new KeyboardSwitcher(this);
- mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher);
- mSystemLocale = conf.locale.toString();
- mLanguageSwitcher.setSystemLocale(conf.locale);
- String inputLanguage = mLanguageSwitcher.getInputLanguage();
- if (inputLanguage == null) {
- inputLanguage = conf.locale.toString();
- }
+ mSubtypeSwitcher = SubtypeSwitcher.getInstance();
+ mKeyboardSwitcher = KeyboardSwitcher.getInstance();
mReCorrectionEnabled = prefs.getBoolean(PREF_RECORRECTION_ENABLED,
getResources().getBoolean(R.bool.default_recorrection_enabled));
@@ -364,10 +352,10 @@ public class LatinIME extends InputMethodService
boolean tryGC = true;
for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
try {
- initSuggest(inputLanguage);
+ initSuggest();
tryGC = false;
} catch (OutOfMemoryError e) {
- tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(inputLanguage, e);
+ tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait("InitSuggest", e);
}
}
@@ -377,19 +365,7 @@ public class LatinIME extends InputMethodService
// register to receive ringer mode changes for silent mode
IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
registerReceiver(mReceiver, filter);
- if (VOICE_INSTALLED) {
- mVoiceInput = new VoiceInput(this, this);
- mHints = new Hints(this, new Hints.Display() {
- public void showHint(int viewResource) {
- LayoutInflater inflater = (LayoutInflater) getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- View view = inflater.inflate(viewResource, null);
- setCandidatesView(view);
- setCandidatesViewShown(true);
- mIsShowingHint = true;
- }
- });
- }
+ mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler);
prefs.registerOnSharedPreferenceChangeListener(this);
}
@@ -397,7 +373,7 @@ public class LatinIME extends InputMethodService
* Loads a dictionary or multiple separated dictionary
* @return returns array of dictionary resource ids
*/
- /* package */ static int[] getDictionary(Resources res) {
+ public static int[] getDictionary(Resources res) {
String packageName = LatinIME.class.getPackage().getName();
XmlResourceParser xrp = res.getXml(R.xml.dictionary);
ArrayList<Integer> dictionaries = new ArrayList<Integer>();
@@ -432,37 +408,34 @@ public class LatinIME extends InputMethodService
return dict;
}
- private void initSuggest(String locale) {
- mInputLocale = locale;
+ private void initSuggest() {
+ updateAutoTextEnabled();
+ String locale = mSubtypeSwitcher.getInputLocaleStr();
Resources orig = getResources();
- Configuration conf = orig.getConfiguration();
- Locale saveLocale = conf.locale;
- conf.locale = new Locale(locale);
- orig.updateConfiguration(conf, orig.getDisplayMetrics());
+ Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(new Locale(locale));
if (mSuggest != null) {
mSuggest.close();
}
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
- mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true);
+ final SharedPreferences prefs = mPrefs;
+ mQuickFixes = prefs.getBoolean(PREF_QUICK_FIXES, true);
int[] dictionaries = getDictionary(orig);
mSuggest = new Suggest(this, dictionaries);
- updateAutoTextEnabled(saveLocale);
+ loadAndSetAutoCompletionThreshold(prefs);
if (mUserDictionary != null) mUserDictionary.close();
- mUserDictionary = new UserDictionary(this, mInputLocale);
+ mUserDictionary = new UserDictionary(this, locale);
if (mContactsDictionary == null) {
mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS);
}
if (mAutoDictionary != null) {
mAutoDictionary.close();
}
- mAutoDictionary = new AutoDictionary(this, this, mInputLocale, Suggest.DIC_AUTO);
+ mAutoDictionary = new AutoDictionary(this, this, locale, Suggest.DIC_AUTO);
if (mUserBigramDictionary != null) {
mUserBigramDictionary.close();
}
- mUserBigramDictionary = new UserBigramDictionary(this, this, mInputLocale,
- Suggest.DIC_USER);
+ mUserBigramDictionary = new UserBigramDictionary(this, this, locale, Suggest.DIC_USER);
mSuggest.setUserBigramDictionary(mUserBigramDictionary);
mSuggest.setUserDictionary(mUserDictionary);
mSuggest.setContactsDictionary(mContactsDictionary);
@@ -471,8 +444,7 @@ public class LatinIME extends InputMethodService
mWordSeparators = mResources.getString(R.string.word_separators);
mSentenceSeparators = mResources.getString(R.string.sentence_separators);
- conf.locale = saveLocale;
- orig.updateConfiguration(conf, orig.getDisplayMetrics());
+ mSubtypeSwitcher.changeSystemLocale(savedLocale);
}
@Override
@@ -484,9 +456,7 @@ public class LatinIME extends InputMethodService
mContactsDictionary.close();
}
unregisterReceiver(mReceiver);
- if (VOICE_INSTALLED && mVoiceInput != null) {
- mVoiceInput.destroy();
- }
+ mVoiceConnector.destroy();
LatinImeLogger.commit();
LatinImeLogger.onDestroy();
super.onDestroy();
@@ -494,51 +464,38 @@ public class LatinIME extends InputMethodService
@Override
public void onConfigurationChanged(Configuration conf) {
- // If the system locale changes and is different from the saved
- // locale (mSystemLocale), then reload the input locale list from the
- // latin ime settings (shared prefs) and reset the input locale
- // to the first one.
- final String systemLocale = conf.locale.toString();
- if (!TextUtils.equals(systemLocale, mSystemLocale)) {
- mSystemLocale = systemLocale;
- if (mLanguageSwitcher != null) {
- mLanguageSwitcher.loadLocales(
- PreferenceManager.getDefaultSharedPreferences(this));
- mLanguageSwitcher.setSystemLocale(conf.locale);
- toggleLanguage(true, true);
- } else {
- reloadKeyboards();
- }
- }
+ mSubtypeSwitcher.onConfigurationChanged(conf);
+ if (mSubtypeSwitcher.isKeyboardMode())
+ onKeyboardLanguageChanged();
+ updateAutoTextEnabled();
+
// If orientation changed while predicting, commit the change
if (conf.orientation != mOrientation) {
InputConnection ic = getCurrentInputConnection();
commitTyped(ic);
if (ic != null) ic.finishComposingText(); // For voice input
mOrientation = conf.orientation;
- reloadKeyboards();
+ final int mode = mKeyboardSwitcher.getKeyboardMode();
+ final EditorInfo attribute = getCurrentInputEditorInfo();
+ final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
+ mKeyboardSwitcher.loadKeyboard(mode, imeOptions,
+ mVoiceConnector.isVoiceButtonEnabled(),
+ mVoiceConnector.isVoiceButtonOnPrimary());
}
+
mConfigurationChanging = true;
super.onConfigurationChanged(conf);
- if (mRecognizing) {
- switchToRecognitionStatusView();
- }
+ mVoiceConnector.onConfigurationChanged(mConfigurationChanging);
mConfigurationChanging = false;
}
@Override
public View onCreateInputView() {
- mKeyboardSwitcher.recreateInputView();
- mKeyboardSwitcher.makeKeyboards(true);
- mKeyboardSwitcher.setKeyboardMode(
- KeyboardSwitcher.MODE_TEXT, 0,
- shouldShowVoiceButton(makeFieldContext(), getCurrentInputEditorInfo()));
- return mKeyboardSwitcher.getInputView();
+ return mKeyboardSwitcher.onCreateInputView();
}
@Override
public View onCreateCandidatesView() {
- mKeyboardSwitcher.makeKeyboards(true);
mCandidateViewContainer = (LinearLayout) getLayoutInflater().inflate(
R.layout.candidates, null);
mCandidateView = (CandidateView) mCandidateViewContainer.findViewById(R.id.candidates);
@@ -547,95 +504,89 @@ public class LatinIME extends InputMethodService
return mCandidateViewContainer;
}
+ private static boolean isPasswordVariation(int variation) {
+ return variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
+ || variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
+ || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD;
+ }
+
+ private static boolean isEmailVariation(int variation) {
+ return variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+ || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
+ }
+
@Override
public void onStartInputView(EditorInfo attribute, boolean restarting) {
- LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
+ final KeyboardSwitcher switcher = mKeyboardSwitcher;
+ LatinKeyboardView inputView = switcher.getInputView();
+
// In landscape mode, this method gets called without the input view being created.
if (inputView == null) {
return;
}
+ mSubtypeSwitcher.updateParametersOnStartInputView();
+
if (mRefreshKeyboardRequired) {
mRefreshKeyboardRequired = false;
- toggleLanguage(true, true);
+ onKeyboardLanguageChanged();
}
- mKeyboardSwitcher.makeKeyboards(false);
-
TextEntryState.newSession(this);
// Most such things we decide below in the switch statement, but we need to know
// now whether this is a password text field, because we need to know now (before
// the switch statement) whether we want to enable the voice button.
- mPasswordText = false;
int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION;
- if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD ||
- variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
- mPasswordText = true;
- }
-
- mEnableVoiceButton = shouldShowVoiceButton(makeFieldContext(), attribute);
- final boolean enableVoiceButton = mEnableVoiceButton && mEnableVoice;
-
- mAfterVoiceInput = false;
- mImmediatelyAfterVoiceInput = false;
- mShowingVoiceSuggestions = false;
- mVoiceInputHighlighted = false;
+ mVoiceConnector.resetVoiceStates(isPasswordVariation(variation));
mInputTypeNoAutoCorrect = false;
mPredictionOn = false;
mCompletionOn = false;
mCompletions = null;
- mCapsLock = false;
mEnteredText = null;
+ final int mode;
switch (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) {
case EditorInfo.TYPE_CLASS_NUMBER:
case EditorInfo.TYPE_CLASS_DATETIME:
- // fall through
- // NOTE: For now, we use the phone keyboard for NUMBER and DATETIME until we get
- // a dedicated number entry keypad.
- // TODO: Use a dedicated number entry keypad here when we get one.
+ mode = KeyboardId.MODE_NUMBER;
+ break;
case EditorInfo.TYPE_CLASS_PHONE:
- mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_PHONE,
- attribute.imeOptions, enableVoiceButton);
+ mode = KeyboardId.MODE_PHONE;
break;
case EditorInfo.TYPE_CLASS_TEXT:
- mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT,
- attribute.imeOptions, enableVoiceButton);
//startPrediction();
mPredictionOn = true;
// Make sure that passwords are not displayed in candidate view
- if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD ||
- variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ) {
+ if (isPasswordVariation(variation)) {
mPredictionOn = false;
}
- if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+ if (isEmailVariation(variation)
|| variation == EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME) {
mAutoSpace = false;
} else {
mAutoSpace = true;
}
- if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) {
+ if (isEmailVariation(variation)) {
mPredictionOn = false;
- mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_EMAIL,
- attribute.imeOptions, enableVoiceButton);
+ mode = KeyboardId.MODE_EMAIL;
} else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) {
mPredictionOn = false;
- mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_URL,
- attribute.imeOptions, enableVoiceButton);
+ mode = KeyboardId.MODE_URL;
} else if (variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
- mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_IM,
- attribute.imeOptions, enableVoiceButton);
+ mode = KeyboardId.MODE_IM;
} else if (variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) {
mPredictionOn = false;
+ mode = KeyboardId.MODE_TEXT;
} else if (variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
- mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_WEB,
- attribute.imeOptions, enableVoiceButton);
+ 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 = KeyboardId.MODE_TEXT;
}
// If NO_SUGGESTIONS is set, don't do prediction.
@@ -654,18 +605,24 @@ public class LatinIME extends InputMethodService
}
break;
default:
- mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT,
- attribute.imeOptions, enableVoiceButton);
+ mode = KeyboardId.MODE_TEXT;
+ break;
}
inputView.closing();
mComposing.setLength(0);
mPredicting = false;
mDeleteCount = 0;
mJustAddedAutoSpace = false;
- loadSettings();
- updateShiftKeyState(attribute);
- setCandidatesViewShownInternal(isCandidateStripVisible() || mCompletionOn,
+ loadSettings(attribute);
+ if (mSubtypeSwitcher.isKeyboardMode()) {
+ switcher.loadKeyboard(mode, attribute.imeOptions,
+ mVoiceConnector.isVoiceButtonEnabled(),
+ mVoiceConnector.isVoiceButtonOnPrimary());
+ switcher.updateShiftState();
+ }
+
+ setCandidatesViewShownInternal(isCandidateStripVisible(),
false /* needsInputViewShown */ );
updateSuggestions();
@@ -676,21 +633,30 @@ public class LatinIME extends InputMethodService
inputView.setPreviewEnabled(mPopupOn);
inputView.setProximityCorrectionEnabled(true);
- mPredictionOn = mPredictionOn && (mCorrectionMode > 0 || mShowSuggestions);
+ mPredictionOn = mPredictionOn && (mCorrectionMode > 0 || isSuggestionShown());
// If we just entered a text field, maybe it has some old text that requires correction
checkReCorrectionOnStart();
checkTutorial(attribute.privateImeOptions);
+ inputView.setForeground(true);
+
+ mVoiceConnector.onStartInputView(mKeyboardSwitcher.getInputView().getWindowToken());
+
if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
}
private void checkReCorrectionOnStart() {
- if (mReCorrectionEnabled && isPredictionOn()) {
+ if (!mReCorrectionEnabled) return;
+
+ final InputConnection ic = getCurrentInputConnection();
+ if (ic == null) return;
+ // There could be a pending composing span. Clean it up first.
+ ic.finishComposingText();
+
+ if (isSuggestionShown() && isPredictionOn()) {
// First get the cursor position. This is required by setOldSuggestions(), so that
// it can pass the correct range to setComposingRegion(). At this point, we don't
// have valid values for mLastSelectionStart/Stop because onUpdateSelection() has
// not been called yet.
- InputConnection ic = getCurrentInputConnection();
- if (ic == null) return;
ExtractedTextRequest etr = new ExtractedTextRequest();
etr.token = 0; // anything is fine here
ExtractedText et = ic.getExtractedText(etr, 0);
@@ -701,7 +667,7 @@ public class LatinIME extends InputMethodService
// Then look for possible corrections in a delayed fashion
if (!TextUtils.isEmpty(et.text) && isCursorTouchingWord()) {
- postUpdateOldSuggestions();
+ mHandler.postUpdateOldSuggestions();
}
}
}
@@ -713,17 +679,10 @@ public class LatinIME extends InputMethodService
LatinImeLogger.commit();
onAutoCompletionStateChanged(false);
- if (VOICE_INSTALLED && !mConfigurationChanging) {
- if (mAfterVoiceInput) {
- mVoiceInput.flushAllTextModificationCounters();
- mVoiceInput.logInputEnded();
- }
- mVoiceInput.flushLogs();
- mVoiceInput.cancel();
- }
- if (mKeyboardSwitcher.getInputView() != null) {
- mKeyboardSwitcher.getInputView().closing();
- }
+ mVoiceConnector.flushVoiceInputLogs(mConfigurationChanging);
+
+ KeyboardView inputView = mKeyboardSwitcher.getInputView();
+ if (inputView != null) inputView.closing();
if (mAutoDictionary != null) mAutoDictionary.flushPendingWrites();
if (mUserBigramDictionary != null) mUserBigramDictionary.flushPendingWrites();
}
@@ -731,21 +690,17 @@ public class LatinIME extends InputMethodService
@Override
public void onFinishInputView(boolean finishingInput) {
super.onFinishInputView(finishingInput);
- // Remove penging messages related to update suggestions
- mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS);
- mHandler.removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
+ KeyboardView inputView = mKeyboardSwitcher.getInputView();
+ if (inputView != null) inputView.setForeground(false);
+ // Remove pending messages related to update suggestions
+ mHandler.cancelUpdateSuggestions();
+ mHandler.cancelUpdateOldSuggestions();
}
@Override
public void onUpdateExtractedText(int token, ExtractedText text) {
super.onUpdateExtractedText(token, text);
- InputConnection ic = getCurrentInputConnection();
- if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) {
- if (mHints.showPunctuationHintIfNecessary(ic)) {
- mVoiceInput.logPunctuationHintDisplayed();
- }
- }
- mImmediatelyAfterVoiceInput = false;
+ mVoiceConnector.showPunctuationHintIfNecessary();
}
@Override
@@ -764,26 +719,22 @@ public class LatinIME extends InputMethodService
+ ", ce=" + candidatesEnd);
}
- if (mAfterVoiceInput) {
- mVoiceInput.setCursorPos(newSelEnd);
- mVoiceInput.setSelectionSpan(newSelEnd - newSelStart);
- }
+ mVoiceConnector.setCursorAndSelection(newSelEnd, newSelStart);
// If the current selection in the text view changes, we should
// clear whatever candidate text we have.
- if ((((mComposing.length() > 0 && mPredicting) || mVoiceInputHighlighted)
- && (newSelStart != candidatesEnd
- || newSelEnd != candidatesEnd)
- && mLastSelectionStart != newSelStart)) {
+ if ((((mComposing.length() > 0 && mPredicting)
+ || mVoiceConnector.isVoiceInputHighlighted()) && (newSelStart != candidatesEnd
+ || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart)) {
mComposing.setLength(0);
mPredicting = false;
- postUpdateSuggestions();
+ mHandler.postUpdateSuggestions();
TextEntryState.reset();
InputConnection ic = getCurrentInputConnection();
if (ic != null) {
ic.finishComposingText();
}
- mVoiceInputHighlighted = false;
+ mVoiceConnector.setVoiceInputHighlighted(false);
} else if (!mPredicting && !mJustAccepted) {
switch (TextEntryState.getState()) {
case ACCEPTED_DEFAULT:
@@ -795,33 +746,29 @@ public class LatinIME extends InputMethodService
}
}
mJustAccepted = false;
- postUpdateShiftKeyState();
+ mHandler.postUpdateShiftKeyState();
// Make a note of the cursor position
mLastSelectionStart = newSelStart;
mLastSelectionEnd = newSelEnd;
- if (mReCorrectionEnabled) {
+ if (mReCorrectionEnabled && isSuggestionShown()) {
// Don't look for corrections if the keyboard is not visible
- if (mKeyboardSwitcher != null && mKeyboardSwitcher.getInputView() != null
- && mKeyboardSwitcher.getInputView().isShown()) {
+ if (mKeyboardSwitcher.isInputViewShown()) {
// Check if we should go in or out of correction mode.
- if (isPredictionOn()
- && mJustRevertedSeparator == null
+ if (isPredictionOn() && !mJustReverted
&& (candidatesStart == candidatesEnd || newSelStart != oldSelStart
|| TextEntryState.isCorrecting())
- && (newSelStart < newSelEnd - 1 || (!mPredicting))
- && !mVoiceInputHighlighted) {
+ && (newSelStart < newSelEnd - 1 || (!mPredicting))) {
if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
- postUpdateOldSuggestions();
+ mHandler.postUpdateOldSuggestions();
} else {
abortCorrection(false);
// Show the punctuation suggestions list if the current one is not
// and if not showing "Touch again to save".
- if (mCandidateView != null
- && !mSuggestPuncList.equals(mCandidateView.getSuggestions())
- && !mCandidateView.isShowingAddToDictionaryHint()) {
- setNextSuggestions();
+ if (mCandidateView != null && !isShowingPunctuationList()
+ && !mCandidateView.isShowingAddToDictionaryHint()) {
+ setPunctuationSuggestions();
}
}
}
@@ -870,18 +817,7 @@ public class LatinIME extends InputMethodService
mOptionsDialog.dismiss();
mOptionsDialog = null;
}
- if (!mConfigurationChanging) {
- if (mAfterVoiceInput) mVoiceInput.logInputEnded();
- if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
- mVoiceInput.logKeyboardWarningDialogDismissed();
- mVoiceWarningDialog.dismiss();
- mVoiceWarningDialog = null;
- }
- if (VOICE_INSTALLED & mRecognizing) {
- mVoiceInput.cancel();
- }
- }
- mWordToSuggestions.clear();
+ mVoiceConnector.hideVoiceWindow(mConfigurationChanging);
mWordHistory.clear();
super.hideWindow();
TextEntryState.endSession();
@@ -917,8 +853,8 @@ public class LatinIME extends InputMethodService
private void setCandidatesViewShownInternal(boolean shown, boolean needsInputViewShown) {
// TODO: Remove this if we support candidates with hard keyboard
if (onEvaluateInputViewShown()) {
- super.setCandidatesViewShown(shown && mKeyboardSwitcher.getInputView() != null
- && (needsInputViewShown ? mKeyboardSwitcher.getInputView().isShown() : true));
+ super.setCandidatesViewShown(shown
+ && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true));
}
}
@@ -985,10 +921,9 @@ public class LatinIME extends InputMethodService
if (mTutorial != null) {
return true;
}
- LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
// Enable shift key and DPAD to do selections
- if (inputView != null && inputView.isShown()
- && inputView.isShifted()) {
+ if (mKeyboardSwitcher.isInputViewShown()
+ && mKeyboardSwitcher.isShiftedOrShiftLocked()) {
event = new KeyEvent(event.getDownTime(), event.getEventTime(),
event.getAction(), event.getKeyCode(), event.getRepeatCount(),
event.getDeviceId(), event.getScanCode(),
@@ -1002,30 +937,7 @@ public class LatinIME extends InputMethodService
return super.onKeyUp(keyCode, event);
}
- private void revertVoiceInput() {
- InputConnection ic = getCurrentInputConnection();
- if (ic != null) ic.commitText("", 1);
- updateSuggestions();
- mVoiceInputHighlighted = false;
- }
-
- private void commitVoiceInput() {
- InputConnection ic = getCurrentInputConnection();
- if (ic != null) ic.finishComposingText();
- updateSuggestions();
- mVoiceInputHighlighted = false;
- }
-
- private void reloadKeyboards() {
- mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher);
- if (mKeyboardSwitcher.getInputView() != null
- && mKeyboardSwitcher.getKeyboardMode() != KeyboardSwitcher.MODE_NONE) {
- mKeyboardSwitcher.setVoiceMode(mEnableVoice && mEnableVoiceButton, mVoiceOnPrimary);
- }
- mKeyboardSwitcher.makeKeyboards(true);
- }
-
- private void commitTyped(InputConnection inputConnection) {
+ public void commitTyped(InputConnection inputConnection) {
if (mPredicting) {
mPredicting = false;
if (mComposing.length() > 0) {
@@ -1040,27 +952,13 @@ public class LatinIME extends InputMethodService
}
}
- private void postUpdateShiftKeyState() {
- mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE);
- // TODO: Should remove this 300ms delay?
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SHIFT_STATE), 300);
- }
-
- public void updateShiftKeyState(EditorInfo attr) {
+ public boolean getCurrentAutoCapsState() {
InputConnection ic = getCurrentInputConnection();
- if (ic != null && attr != null && mKeyboardSwitcher.isAlphabetMode()) {
- mKeyboardSwitcher.setShifted(mShiftKeyState.isMomentary() || mCapsLock
- || getCursorCapsMode(ic, attr) != 0);
- }
- }
-
- private int getCursorCapsMode(InputConnection ic, EditorInfo attr) {
- int caps = 0;
EditorInfo ei = getCurrentInputEditorInfo();
- if (mAutoCap && ei != null && ei.inputType != EditorInfo.TYPE_NULL) {
- caps = ic.getCursorCapsMode(attr.inputType);
+ if (mAutoCap && ic != null && ei != null && ei.inputType != EditorInfo.TYPE_NULL) {
+ return ic.getCursorCapsMode(ei.inputType) != 0;
}
- return caps;
+ return false;
}
private void swapPunctuationAndSpace() {
@@ -1068,12 +966,13 @@ public class LatinIME extends InputMethodService
if (ic == null) return;
CharSequence lastTwo = ic.getTextBeforeCursor(2, 0);
if (lastTwo != null && lastTwo.length() == 2
- && lastTwo.charAt(0) == KEYCODE_SPACE && isSentenceSeparator(lastTwo.charAt(1))) {
+ && lastTwo.charAt(0) == Keyboard.CODE_SPACE
+ && isSentenceSeparator(lastTwo.charAt(1))) {
ic.beginBatchEdit();
ic.deleteSurroundingText(2, 0);
ic.commitText(lastTwo.charAt(1) + " ", 1);
ic.endBatchEdit();
- updateShiftKeyState(getCurrentInputEditorInfo());
+ mKeyboardSwitcher.updateShiftState();
mJustAddedAutoSpace = true;
}
}
@@ -1083,14 +982,14 @@ public class LatinIME extends InputMethodService
if (ic == null) return;
CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
if (lastThree != null && lastThree.length() == 3
- && lastThree.charAt(0) == KEYCODE_PERIOD
- && lastThree.charAt(1) == KEYCODE_SPACE
- && lastThree.charAt(2) == KEYCODE_PERIOD) {
+ && lastThree.charAt(0) == Keyboard.CODE_PERIOD
+ && lastThree.charAt(1) == Keyboard.CODE_SPACE
+ && lastThree.charAt(2) == Keyboard.CODE_PERIOD) {
ic.beginBatchEdit();
ic.deleteSurroundingText(3, 0);
ic.commitText(" ..", 1);
ic.endBatchEdit();
- updateShiftKeyState(getCurrentInputEditorInfo());
+ mKeyboardSwitcher.updateShiftState();
}
}
@@ -1102,12 +1001,13 @@ public class LatinIME extends InputMethodService
CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
if (lastThree != null && lastThree.length() == 3
&& Character.isLetterOrDigit(lastThree.charAt(0))
- && lastThree.charAt(1) == KEYCODE_SPACE && lastThree.charAt(2) == KEYCODE_SPACE) {
+ && lastThree.charAt(1) == Keyboard.CODE_SPACE
+ && lastThree.charAt(2) == Keyboard.CODE_SPACE) {
ic.beginBatchEdit();
ic.deleteSurroundingText(2, 0);
ic.commitText(". ", 1);
ic.endBatchEdit();
- updateShiftKeyState(getCurrentInputEditorInfo());
+ mKeyboardSwitcher.updateShiftState();
mJustAddedAutoSpace = true;
}
}
@@ -1120,8 +1020,8 @@ public class LatinIME extends InputMethodService
// if there is one.
CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
if (lastOne != null && lastOne.length() == 1
- && lastOne.charAt(0) == KEYCODE_PERIOD
- && text.charAt(0) == KEYCODE_PERIOD) {
+ && lastOne.charAt(0) == Keyboard.CODE_PERIOD
+ && text.charAt(0) == Keyboard.CODE_PERIOD) {
ic.deleteSurroundingText(1, 0);
}
}
@@ -1132,7 +1032,7 @@ public class LatinIME extends InputMethodService
CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
if (lastOne != null && lastOne.length() == 1
- && lastOne.charAt(0) == KEYCODE_SPACE) {
+ && lastOne.charAt(0) == Keyboard.CODE_SPACE) {
ic.deleteSurroundingText(1, 0);
}
}
@@ -1141,7 +1041,7 @@ public class LatinIME extends InputMethodService
mUserDictionary.addWord(word, 128);
// Suggestion strip should be updated after the operation of adding word to the
// user dictionary
- postUpdateSuggestions();
+ mHandler.postUpdateSuggestions();
return true;
}
@@ -1153,14 +1053,9 @@ public class LatinIME extends InputMethodService
}
}
- private void showInputMethodPicker() {
- ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE))
- .showInputMethodPicker();
- }
-
- private void onOptionKeyPressed() {
+ private void onSettingsKeyPressed() {
if (!isShowingOptionDialog()) {
- if (LatinIMEUtil.hasMultipleEnabledIMEs(this)) {
+ if (LatinIMEUtil.hasMultipleEnabledIMEsOrSubtypes(mImm)) {
showOptionsMenu();
} else {
launchSettings();
@@ -1168,10 +1063,10 @@ public class LatinIME extends InputMethodService
}
}
- private void onOptionKeyLongPressed() {
+ private void onSettingsKeyLongPressed() {
if (!isShowingOptionDialog()) {
- if (LatinIMEUtil.hasMultipleEnabledIMEs(this)) {
- showInputMethodPicker();
+ if (LatinIMEUtil.hasMultipleEnabledIMEsOrSubtypes(mImm)) {
+ mImm.showInputMethodPicker();
} else {
launchSettings();
}
@@ -1184,80 +1079,80 @@ 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 != Keyboard.KEYCODE_DELETE ||
- when > mLastKeyTime + QUICK_PRESS) {
+ if (primaryCode != Keyboard.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) {
mDeleteCount = 0;
}
mLastKeyTime = when;
- final boolean distinctMultiTouch = mKeyboardSwitcher.hasDistinctMultitouch();
+ KeyboardSwitcher switcher = mKeyboardSwitcher;
+ final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
switch (primaryCode) {
- case Keyboard.KEYCODE_DELETE:
- handleBackspace();
- mDeleteCount++;
- LatinImeLogger.logOnDelete();
- break;
- case Keyboard.KEYCODE_SHIFT:
- // Shift key is handled in onPress() when device has distinct multi-touch panel.
- if (!distinctMultiTouch)
- handleShift();
- break;
- case Keyboard.KEYCODE_MODE_CHANGE:
- // Symbol key is handled in onPress() when device has distinct multi-touch panel.
- if (!distinctMultiTouch)
- changeKeyboardMode();
- break;
- case Keyboard.KEYCODE_CANCEL:
- if (!isShowingOptionDialog()) {
- handleClose();
- }
- break;
- case LatinKeyboardView.KEYCODE_OPTIONS:
- onOptionKeyPressed();
- break;
- case LatinKeyboardView.KEYCODE_OPTIONS_LONGPRESS:
- onOptionKeyLongPressed();
- break;
- case LatinKeyboardView.KEYCODE_NEXT_LANGUAGE:
- toggleLanguage(false, true);
- break;
- case LatinKeyboardView.KEYCODE_PREV_LANGUAGE:
- toggleLanguage(false, false);
- break;
- case LatinKeyboardView.KEYCODE_VOICE:
- if (VOICE_INSTALLED) {
- startListening(false /* was a button press, was not a swipe */);
- }
- break;
- case 9 /*Tab*/:
- sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
- break;
- default:
- if (primaryCode != KEYCODE_ENTER) {
- mJustAddedAutoSpace = false;
- }
- RingCharBuffer.getInstance().push((char)primaryCode, x, y);
- LatinImeLogger.logOnInputChar();
- if (isWordSeparator(primaryCode)) {
- handleSeparator(primaryCode);
- } else {
- handleCharacter(primaryCode, keyCodes);
- }
- // Cancel the just reverted state
- mJustRevertedSeparator = null;
- }
- if (mKeyboardSwitcher.onKey(primaryCode)) {
- changeKeyboardMode();
+ case Keyboard.CODE_DELETE:
+ handleBackspace();
+ mDeleteCount++;
+ LatinImeLogger.logOnDelete();
+ break;
+ case Keyboard.CODE_SHIFT:
+ // Shift key is handled in onPress() when device has distinct multi-touch panel.
+ if (!distinctMultiTouch)
+ switcher.toggleShift();
+ break;
+ case Keyboard.CODE_SWITCH_ALPHA_SYMBOL:
+ // Symbol key is handled in onPress() when device has distinct multi-touch panel.
+ if (!distinctMultiTouch)
+ switcher.changeKeyboardMode();
+ break;
+ case Keyboard.CODE_CANCEL:
+ if (!isShowingOptionDialog()) {
+ handleClose();
+ }
+ break;
+ case Keyboard.CODE_SETTINGS:
+ onSettingsKeyPressed();
+ break;
+ case Keyboard.CODE_SETTINGS_LONGPRESS:
+ onSettingsKeyLongPressed();
+ break;
+ case Keyboard.CODE_NEXT_LANGUAGE:
+ toggleLanguage(false, true);
+ break;
+ case Keyboard.CODE_PREV_LANGUAGE:
+ toggleLanguage(false, false);
+ break;
+ case Keyboard.CODE_CAPSLOCK:
+ switcher.toggleCapsLock();
+ break;
+ case Keyboard.CODE_VOICE: /* was a button press, was not a swipe */
+ mVoiceConnector.startListening(false,
+ mKeyboardSwitcher.getInputView().getWindowToken(), mConfigurationChanging);
+ break;
+ case Keyboard.CODE_TAB:
+ handleTab();
+ break;
+ default:
+ if (primaryCode != Keyboard.CODE_ENTER) {
+ mJustAddedAutoSpace = false;
+ }
+ RingCharBuffer.getInstance().push((char)primaryCode, x, y);
+ LatinImeLogger.logOnInputChar();
+ if (isWordSeparator(primaryCode)) {
+ handleSeparator(primaryCode);
+ } else {
+ handleCharacter(primaryCode, keyCodes);
+ }
+ // Cancel the just reverted state
+ mJustReverted = false;
}
+ switcher.onKey(primaryCode);
// Reset after any single keystroke
mEnteredText = null;
}
+ @Override
public void onText(CharSequence text) {
- if (VOICE_INSTALLED && mVoiceInputHighlighted) {
- commitVoiceInput();
- }
+ mVoiceConnector.commitVoiceInput();
InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
abortCorrection(false);
@@ -1268,40 +1163,26 @@ public class LatinIME extends InputMethodService
maybeRemovePreviousPeriod(text);
ic.commitText(text, 1);
ic.endBatchEdit();
- updateShiftKeyState(getCurrentInputEditorInfo());
- mJustRevertedSeparator = null;
+ mKeyboardSwitcher.updateShiftState();
+ mJustReverted = false;
mJustAddedAutoSpace = false;
mEnteredText = text;
}
+ @Override
public void onCancel() {
// User released a finger outside any key
}
private void handleBackspace() {
- if (VOICE_INSTALLED && mVoiceInputHighlighted) {
- mVoiceInput.incrementTextModificationDeleteCount(
- mVoiceResults.candidates.get(0).toString().length());
- revertVoiceInput();
- return;
- }
+ if (mVoiceConnector.logAndRevertVoiceInput()) return;
boolean deleteChar = false;
InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
ic.beginBatchEdit();
- if (mAfterVoiceInput) {
- // Don't log delete if the user is pressing delete at
- // the beginning of the text box (hence not deleting anything)
- if (mVoiceInput.getCursorPos() > 0) {
- // If anything was selected before the delete was pressed, increment the
- // delete count by the length of the selection
- int deleteLen = mVoiceInput.getSelectionSpan() > 0 ?
- mVoiceInput.getSelectionSpan() : 1;
- mVoiceInput.incrementTextModificationDeleteCount(deleteLen);
- }
- }
+ mVoiceConnector.handleBackspace();
if (mPredicting) {
final int length = mComposing.length();
@@ -1312,14 +1193,14 @@ public class LatinIME extends InputMethodService
if (mComposing.length() == 0) {
mPredicting = false;
}
- postUpdateSuggestions();
+ mHandler.postUpdateSuggestions();
} else {
ic.deleteSurroundingText(1, 0);
}
} else {
deleteChar = true;
}
- postUpdateShiftKeyState();
+ mHandler.postUpdateShiftKeyState();
TextEntryState.backspace();
if (TextEntryState.getState() == TextEntryState.State.UNDO_COMMIT) {
revertLastWord(deleteChar);
@@ -1344,55 +1225,46 @@ public class LatinIME extends InputMethodService
}
}
}
- mJustRevertedSeparator = null;
+ mJustReverted = false;
ic.endBatchEdit();
}
- private void resetShift() {
- handleShiftInternal(true);
- }
+ private void handleTab() {
+ final int imeOptions = getCurrentInputEditorInfo().imeOptions;
+ final int navigationFlags =
+ EditorInfo.IME_FLAG_NAVIGATE_NEXT | EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
+ if ((imeOptions & navigationFlags) == 0) {
+ sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
+ return;
+ }
- private void handleShift() {
- handleShiftInternal(false);
- }
+ final InputConnection ic = getCurrentInputConnection();
+ if (ic == null)
+ return;
- private void handleShiftInternal(boolean forceNormal) {
- mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE);
- KeyboardSwitcher switcher = mKeyboardSwitcher;
- LatinKeyboardView inputView = switcher.getInputView();
- if (switcher.isAlphabetMode()) {
- if (mCapsLock || forceNormal) {
- mCapsLock = false;
- switcher.setShifted(false);
- } else if (inputView != null) {
- if (inputView.isShifted()) {
- mCapsLock = true;
- switcher.setShiftLocked(true);
- } else {
- switcher.setShifted(true);
- }
- }
- } else {
- switcher.toggleShift();
+ // True if keyboard is in either chording shift or manual temporary upper case mode.
+ final boolean isManualTemporaryUpperCase = mKeyboardSwitcher.isManualTemporaryUpperCase();
+ if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0
+ && !isManualTemporaryUpperCase) {
+ ic.performEditorAction(EditorInfo.IME_ACTION_NEXT);
+ } else if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0
+ && isManualTemporaryUpperCase) {
+ ic.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
}
}
private void abortCorrection(boolean force) {
if (force || TextEntryState.isCorrecting()) {
+ TextEntryState.onAbortCorrection();
+ setCandidatesViewShown(isCandidateStripVisible());
getCurrentInputConnection().finishComposingText();
clearSuggestions();
}
}
private void handleCharacter(int primaryCode, int[] keyCodes) {
- if (VOICE_INSTALLED && mVoiceInputHighlighted) {
- commitVoiceInput();
- }
+ mVoiceConnector.handleCharacter();
- if (mAfterVoiceInput) {
- // Assume input length is 1. This assumption fails for smiley face insertions.
- mVoiceInput.incrementTextModificationInsertCount(1);
- }
if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) {
abortCorrection(false);
}
@@ -1405,13 +1277,14 @@ public class LatinIME extends InputMethodService
mWord.reset();
}
}
- if (mKeyboardSwitcher.getInputView().isShifted()) {
+ KeyboardSwitcher switcher = mKeyboardSwitcher;
+ if (switcher.isShiftedOrShiftLocked()) {
if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT
|| keyCodes[0] > Character.MAX_CODE_POINT) {
return;
}
primaryCode = keyCodes[0];
- if (mKeyboardSwitcher.isAlphabetMode() && Character.isLowerCase(primaryCode)) {
+ if (switcher.isAlphabetMode() && Character.isLowerCase(primaryCode)) {
int upperCaseCode = Character.toUpperCase(primaryCode);
if (upperCaseCode != primaryCode) {
primaryCode = upperCaseCode;
@@ -1424,9 +1297,8 @@ public class LatinIME extends InputMethodService
}
}
if (mPredicting) {
- if (mKeyboardSwitcher.getInputView().isShifted()
- && mKeyboardSwitcher.isAlphabetMode()
- && mComposing.length() == 0) {
+ if (mComposing.length() == 0 && switcher.isAlphabetMode()
+ && switcher.isShiftedOrShiftLocked()) {
mWord.setFirstCharCapitalized(true);
}
mComposing.append((char) primaryCode);
@@ -1435,33 +1307,25 @@ public class LatinIME extends InputMethodService
if (ic != null) {
// If it's the first letter, make note of auto-caps state
if (mWord.size() == 1) {
- mWord.setAutoCapitalized(
- getCursorCapsMode(ic, getCurrentInputEditorInfo()) != 0);
+ mWord.setAutoCapitalized(getCurrentAutoCapsState());
}
ic.setComposingText(mComposing, 1);
}
- postUpdateSuggestions();
+ mHandler.postUpdateSuggestions();
} else {
sendKeyChar((char)primaryCode);
}
- updateShiftKeyState(getCurrentInputEditorInfo());
+ switcher.updateShiftState();
if (LatinIME.PERF_DEBUG) measureCps();
TextEntryState.typedCharacter((char) primaryCode, isWordSeparator(primaryCode));
}
private void handleSeparator(int primaryCode) {
- if (VOICE_INSTALLED && mVoiceInputHighlighted) {
- commitVoiceInput();
- }
-
- if (mAfterVoiceInput){
- // Assume input length is 1. This assumption fails for smiley face insertions.
- mVoiceInput.incrementTextModificationInsertPunctuationCount(1);
- }
+ mVoiceConnector.handleSeparator();
// Should dismiss the "Touch again to save" message when handling separator
if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) {
- postUpdateSuggestions();
+ mHandler.postUpdateSuggestions();
}
boolean pickedDefault = false;
@@ -1476,21 +1340,18 @@ public class LatinIME extends InputMethodService
// not to auto correct, but accept the typed word. For instance,
// in Italian dov' should not be expanded to dove' because the elision
// requires the last vowel to be removed.
- if (mAutoCorrectOn && primaryCode != '\'' &&
- (mJustRevertedSeparator == null
- || mJustRevertedSeparator.length() == 0
- || mJustRevertedSeparator.charAt(0) != primaryCode)) {
+ if (mAutoCorrectOn && primaryCode != '\'' && !mJustReverted) {
pickedDefault = pickDefaultSuggestion();
// Picked the suggestion by the space key. We consider this
// as "added an auto space".
- if (primaryCode == KEYCODE_SPACE) {
+ if (primaryCode == Keyboard.CODE_SPACE) {
mJustAddedAutoSpace = true;
}
} else {
commitTyped(ic);
}
}
- if (mJustAddedAutoSpace && primaryCode == KEYCODE_ENTER) {
+ if (mJustAddedAutoSpace && primaryCode == Keyboard.CODE_ENTER) {
removeTrailingSpace();
mJustAddedAutoSpace = false;
}
@@ -1499,21 +1360,21 @@ public class LatinIME extends InputMethodService
// Handle the case of ". ." -> " .." with auto-space if necessary
// before changing the TextEntryState.
if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED
- && primaryCode == KEYCODE_PERIOD) {
+ && primaryCode == Keyboard.CODE_PERIOD) {
reswapPeriodAndSpace();
}
TextEntryState.typedCharacter((char) primaryCode, true);
if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED
- && primaryCode != KEYCODE_ENTER) {
+ && primaryCode != Keyboard.CODE_ENTER) {
swapPunctuationAndSpace();
- } else if (isPredictionOn() && primaryCode == KEYCODE_SPACE) {
+ } else if (isPredictionOn() && primaryCode == Keyboard.CODE_SPACE) {
doubleSpace();
}
if (pickedDefault) {
TextEntryState.backToAcceptedDefault(mWord.getTypedWord());
}
- updateShiftKeyState(getCurrentInputEditorInfo());
+ mKeyboardSwitcher.updateShiftState();
if (ic != null) {
ic.endBatchEdit();
}
@@ -1521,16 +1382,11 @@ public class LatinIME extends InputMethodService
private void handleClose() {
commitTyped(getCurrentInputConnection());
- if (VOICE_INSTALLED & mRecognizing) {
- mVoiceInput.cancel();
- }
+ mVoiceConnector.handleClose();
requestHideSelf(0);
- if (mKeyboardSwitcher != null) {
- LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
- if (inputView != null) {
- inputView.closing();
- }
- }
+ LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
+ if (inputView != null)
+ inputView.closing();
TextEntryState.endSession();
}
@@ -1551,216 +1407,62 @@ public class LatinIME extends InputMethodService
mWordHistory.add(entry);
}
- private void postUpdateSuggestions() {
- mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SUGGESTIONS), 100);
- }
-
- private void postUpdateOldSuggestions() {
- mHandler.removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS), 300);
- }
-
private boolean isPredictionOn() {
return mPredictionOn;
}
- private boolean isCandidateStripVisible() {
- return isPredictionOn() && mShowSuggestions;
+ private boolean isShowingPunctuationList() {
+ return mSuggestPuncList.equals(mCandidateView.getSuggestions());
}
- public void onCancelVoice() {
- if (mRecognizing) {
- switchToKeyboardView();
- }
+ private boolean isSuggestionShown() {
+ return (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_VALUE)
+ || (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE
+ && mOrientation == Configuration.ORIENTATION_PORTRAIT);
}
- private void switchToKeyboardView() {
- mHandler.post(new Runnable() {
- public void run() {
- mRecognizing = false;
- if (mKeyboardSwitcher.getInputView() != null) {
- setInputView(mKeyboardSwitcher.getInputView());
- }
- setCandidatesViewShown(true);
- updateInputViewShown();
- postUpdateSuggestions();
- }});
+ private boolean isCandidateStripVisible() {
+ boolean forceVisible = mCandidateView.isShowingAddToDictionaryHint()
+ || TextEntryState.isCorrecting();
+ return forceVisible || (isSuggestionShown()
+ && (isPredictionOn() || mCompletionOn || isShowingPunctuationList()));
}
- private void switchToRecognitionStatusView() {
- final boolean configChanged = mConfigurationChanging;
+ public void switchToKeyboardView() {
mHandler.post(new Runnable() {
+ @Override
public void run() {
- setCandidatesViewShown(false);
- mRecognizing = true;
- View v = mVoiceInput.getView();
- ViewParent p = v.getParent();
- if (p != null && p instanceof ViewGroup) {
- ((ViewGroup)v.getParent()).removeView(v);
+ if (DEBUG) {
+ Log.d(TAG, "Switch to keyboard view.");
}
- setInputView(v);
- updateInputViewShown();
- if (configChanged) {
- mVoiceInput.onConfigurationChanged();
+ 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);
}
- }});
- }
-
- private void startListening(boolean swipe) {
- if (!mHasUsedVoiceInput ||
- (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale)) {
- // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel.
- showVoiceWarningDialog(swipe);
- } else {
- reallyStartListening(swipe);
- }
- }
-
- private void reallyStartListening(boolean swipe) {
- if (!mHasUsedVoiceInput) {
- // The user has started a voice input, so remember that in the
- // future (so we don't show the warning dialog after the first run).
- SharedPreferences.Editor editor =
- PreferenceManager.getDefaultSharedPreferences(this).edit();
- editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true);
- SharedPreferencesCompat.apply(editor);
- mHasUsedVoiceInput = true;
- }
-
- if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) {
- // The user has started a voice input from an unsupported locale, so remember that
- // in the future (so we don't show the warning dialog the next time they do this).
- SharedPreferences.Editor editor =
- PreferenceManager.getDefaultSharedPreferences(this).edit();
- editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true);
- SharedPreferencesCompat.apply(editor);
- mHasUsedVoiceInputUnsupportedLocale = true;
- }
-
- // Clear N-best suggestions
- clearSuggestions();
-
- FieldContext context = new FieldContext(
- getCurrentInputConnection(),
- getCurrentInputEditorInfo(),
- mLanguageSwitcher.getInputLanguage(),
- mLanguageSwitcher.getEnabledLanguages());
- mVoiceInput.startListening(context, swipe);
- switchToRecognitionStatusView();
- }
-
- private void showVoiceWarningDialog(final boolean swipe) {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setCancelable(true);
- builder.setIcon(R.drawable.ic_mic_dialog);
- builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int whichButton) {
- mVoiceInput.logKeyboardWarningDialogOk();
- reallyStartListening(swipe);
- }
- });
- builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int whichButton) {
- mVoiceInput.logKeyboardWarningDialogCancel();
+ setCandidatesViewShown(isCandidateStripVisible());
+ updateInputViewShown();
+ mHandler.postUpdateSuggestions();
}
});
-
- if (mLocaleSupportedForVoiceInput) {
- String message = getString(R.string.voice_warning_may_not_understand) + "\n\n" +
- getString(R.string.voice_warning_how_to_turn_off);
- builder.setMessage(message);
- } else {
- String message = getString(R.string.voice_warning_locale_not_supported) + "\n\n" +
- getString(R.string.voice_warning_may_not_understand) + "\n\n" +
- getString(R.string.voice_warning_how_to_turn_off);
- builder.setMessage(message);
- }
-
- builder.setTitle(R.string.voice_warning_title);
- mVoiceWarningDialog = builder.create();
-
- Window window = mVoiceWarningDialog.getWindow();
- WindowManager.LayoutParams lp = window.getAttributes();
- lp.token = mKeyboardSwitcher.getInputView().getWindowToken();
- lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
- window.setAttributes(lp);
- window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
- mVoiceInput.logKeyboardWarningDialogShown();
- mVoiceWarningDialog.show();
- }
-
- public void onVoiceResults(List<String> candidates,
- Map<String, List<CharSequence>> alternatives) {
- if (!mRecognizing) {
- return;
- }
- mVoiceResults.candidates = candidates;
- mVoiceResults.alternatives = alternatives;
- mHandler.sendMessage(mHandler.obtainMessage(MSG_VOICE_RESULTS));
}
- private void handleVoiceResults() {
- mAfterVoiceInput = true;
- mImmediatelyAfterVoiceInput = true;
-
- InputConnection ic = getCurrentInputConnection();
- if (!isFullscreenMode()) {
- // Start listening for updates to the text from typing, etc.
- if (ic != null) {
- ExtractedTextRequest req = new ExtractedTextRequest();
- ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR);
- }
- }
-
- vibrate();
- switchToKeyboardView();
-
- final List<CharSequence> nBest = new ArrayList<CharSequence>();
- boolean capitalizeFirstWord = preferCapitalization()
- || (mKeyboardSwitcher.isAlphabetMode()
- && mKeyboardSwitcher.getInputView().isShifted());
- for (String c : mVoiceResults.candidates) {
- if (capitalizeFirstWord) {
- c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length());
- }
- nBest.add(c);
- }
-
- if (nBest.size() == 0) {
- return;
- }
-
- String bestResult = nBest.get(0).toString();
-
- mVoiceInput.logVoiceInputDelivered(bestResult.length());
-
- mHints.registerVoiceResult(bestResult);
-
- if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text
-
- commitTyped(ic);
- EditingUtil.appendText(ic, bestResult);
-
- if (ic != null) ic.endBatchEdit();
-
- mVoiceInputHighlighted = true;
- mWordToSuggestions.putAll(mVoiceResults.alternatives);
- }
-
- private void clearSuggestions() {
+ public void clearSuggestions() {
setSuggestions(null, false, false, false);
}
- private void setSuggestions(
+ public void setSuggestions(
List<CharSequence> suggestions,
boolean completions,
boolean typedWordValid,
boolean haveMinimalSuggestion) {
- if (mIsShowingHint) {
+ if (mVoiceConnector.getAndResetIsShowingHint()) {
setCandidatesView(mCandidateViewContainer);
- mIsShowingHint = false;
}
if (mCandidateView != null) {
@@ -1769,17 +1471,17 @@ public class LatinIME extends InputMethodService
}
}
- private void updateSuggestions() {
- LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
- ((LatinKeyboard) inputView.getKeyboard()).setPreferredLetters(null);
+ public void updateSuggestions() {
+ mKeyboardSwitcher.setPreferredLetters(null);
// Check if we have a suggestion engine attached.
- if ((mSuggest == null || !isPredictionOn()) && !mVoiceInputHighlighted) {
+ if ((mSuggest == null || !isPredictionOn())
+ && !mVoiceConnector.isVoiceInputHighlighted()) {
return;
}
if (!mPredicting) {
- setNextSuggestions();
+ setPunctuationSuggestions();
return;
}
showSuggestions(mWord);
@@ -1792,8 +1494,8 @@ public class LatinIME extends InputMethodService
}
private void showCorrections(WordAlternatives alternatives) {
+ mKeyboardSwitcher.setPreferredLetters(null);
List<CharSequence> stringList = alternatives.getAlternatives();
- ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).setPreferredLetters(null);
showSuggestions(stringList, alternatives.getOriginalWord(), false, false);
}
@@ -1808,12 +1510,10 @@ public class LatinIME extends InputMethodService
// Log.d("LatinIME","Suggest Total Time - " + (stopTime - startTime));
int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies();
+ mKeyboardSwitcher.setPreferredLetters(nextLettersFrequencies);
- ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).setPreferredLetters(
- nextLettersFrequencies);
-
- boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasMinimalCorrection();
- //|| mCorrectionMode == mSuggest.CORRECTION_FULL;
+ boolean correctionAvailable = !mInputTypeNoAutoCorrect && !mJustReverted
+ && mSuggest.hasMinimalCorrection();
CharSequence typedWord = word.getTypedWord();
// If we're in basic correct
boolean typedWordValid = mSuggest.isValidWord(typedWord) ||
@@ -1842,13 +1542,13 @@ public class LatinIME extends InputMethodService
} else {
mBestWord = null;
}
- setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn);
+ setCandidatesViewShown(isCandidateStripVisible());
}
private boolean pickDefaultSuggestion() {
// Complete any pending candidate query first
- if (mHandler.hasMessages(MSG_UPDATE_SUGGESTIONS)) {
- mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS);
+ if (mHandler.hasPendingUpdateSuggestions()) {
+ mHandler.cancelUpdateSuggestions();
updateSuggestions();
}
if (mBestWord != null && mBestWord.length() > 0) {
@@ -1864,14 +1564,8 @@ public class LatinIME extends InputMethodService
}
public void pickSuggestionManually(int index, CharSequence suggestion) {
- if (mAfterVoiceInput && mShowingVoiceSuggestions) mVoiceInput.logNBestChoose(index);
List<CharSequence> suggestions = mCandidateView.getSuggestions();
-
- if (mAfterVoiceInput && !mShowingVoiceSuggestions) {
- mVoiceInput.flushAllTextModificationCounters();
- // send this intent AFTER logging any prior aggregated edits.
- mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.length());
- }
+ mVoiceConnector.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators);
final boolean correcting = TextEntryState.isCorrecting();
InputConnection ic = getCurrentInputConnection();
@@ -1888,7 +1582,7 @@ public class LatinIME extends InputMethodService
if (mCandidateView != null) {
mCandidateView.clear();
}
- updateShiftKeyState(getCurrentInputEditorInfo());
+ mKeyboardSwitcher.updateShiftState();
if (ic != null) {
ic.endBatchEdit();
}
@@ -1903,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}, LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE,
- LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE);
+ onKey(primaryCode, new int[]{primaryCode}, KeyboardView.NOT_A_TOUCH_COORDINATE,
+ KeyboardView.NOT_A_TOUCH_COORDINATE);
if (ic != null) {
ic.endBatchEdit();
}
@@ -1935,13 +1629,13 @@ public class LatinIME extends InputMethodService
// Fool the state watcher so that a subsequent backspace will not do a revert, unless
// we just did a correction, in which case we need to stay in
// TextEntryState.State.PICKED_SUGGESTION state.
- TextEntryState.typedCharacter((char) KEYCODE_SPACE, true);
- setNextSuggestions();
+ TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true);
+ setPunctuationSuggestions();
} else if (!showingAddToDictionaryHint) {
// If we're not showing the "Touch again to save", then show corrections again.
// In case the cursor position doesn't change, make sure we show the suggestions again.
clearSuggestions();
- postUpdateOldSuggestions();
+ mHandler.postUpdateOldSuggestions();
}
if (showingAddToDictionaryHint) {
mCandidateView.showAddToDictionaryHint(suggestion);
@@ -1951,27 +1645,6 @@ public class LatinIME extends InputMethodService
}
}
- private void rememberReplacedWord(CharSequence suggestion) {
- if (mShowingVoiceSuggestions) {
- // Retain the replaced word in the alternatives array.
- EditingUtil.Range range = new EditingUtil.Range();
- String wordToBeReplaced = EditingUtil.getWordAtCursor(getCurrentInputConnection(),
- mWordSeparators, range);
- if (!mWordToSuggestions.containsKey(wordToBeReplaced)) {
- wordToBeReplaced = wordToBeReplaced.toLowerCase();
- }
- if (mWordToSuggestions.containsKey(wordToBeReplaced)) {
- List<CharSequence> suggestions = mWordToSuggestions.get(wordToBeReplaced);
- if (suggestions.contains(suggestion)) {
- suggestions.remove(suggestion);
- }
- suggestions.add(wordToBeReplaced);
- mWordToSuggestions.remove(wordToBeReplaced);
- mWordToSuggestions.put(suggestion.toString(), suggestions);
- }
- }
- }
-
/**
* Commits the chosen word to the text field and saves it for later
* retrieval.
@@ -1981,61 +1654,23 @@ public class LatinIME extends InputMethodService
* word.
*/
private void pickSuggestion(CharSequence suggestion, boolean correcting) {
- LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
- if (mCapsLock) {
- suggestion = suggestion.toString().toUpperCase();
- } else if (preferCapitalization()
- || (mKeyboardSwitcher.isAlphabetMode()
- && inputView.isShifted())) {
- suggestion = suggestion.toString().toUpperCase().charAt(0)
- + suggestion.subSequence(1, suggestion.length()).toString();
- }
+ KeyboardSwitcher switcher = mKeyboardSwitcher;
+ if (!switcher.isKeyboardAvailable())
+ return;
InputConnection ic = getCurrentInputConnection();
if (ic != null) {
- rememberReplacedWord(suggestion);
+ mVoiceConnector.rememberReplacedWord(suggestion, mWordSeparators);
ic.commitText(suggestion, 1);
}
saveWordInHistory(suggestion);
mPredicting = false;
mCommittedLength = suggestion.length();
- ((LatinKeyboard) inputView.getKeyboard()).setPreferredLetters(null);
+ switcher.setPreferredLetters(null);
// If we just corrected a word, then don't show punctuations
if (!correcting) {
- setNextSuggestions();
+ setPunctuationSuggestions();
}
- updateShiftKeyState(getCurrentInputEditorInfo());
- }
-
- /**
- * Tries to apply any voice alternatives for the word if this was a spoken word and
- * there are voice alternatives.
- * @param touching The word that the cursor is touching, with position information
- * @return true if an alternative was found, false otherwise.
- */
- private boolean applyVoiceAlternatives(EditingUtil.SelectedWord touching) {
- // Search for result in spoken word alternatives
- String selectedWord = touching.word.toString().trim();
- if (!mWordToSuggestions.containsKey(selectedWord)) {
- selectedWord = selectedWord.toLowerCase();
- }
- if (mWordToSuggestions.containsKey(selectedWord)) {
- mShowingVoiceSuggestions = true;
- List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord);
- // If the first letter of touching is capitalized, make all the suggestions
- // start with a capital letter.
- if (Character.isUpperCase(touching.word.charAt(0))) {
- for (int i = 0; i < suggestions.size(); i++) {
- String origSugg = (String) suggestions.get(i);
- String capsSugg = origSugg.toUpperCase().charAt(0)
- + origSugg.subSequence(1, origSugg.length()).toString();
- suggestions.set(i, capsSugg);
- }
- }
- setSuggestions(suggestions, false, true, true);
- setCandidatesViewShown(true);
- return true;
- }
- return false;
+ switcher.updateShiftState();
}
/**
@@ -2086,7 +1721,7 @@ public class LatinIME extends InputMethodService
}
private void setOldSuggestions() {
- mShowingVoiceSuggestions = false;
+ mVoiceConnector.setShowingVoiceSuggestions(false);
if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) {
return;
}
@@ -2100,7 +1735,8 @@ public class LatinIME extends InputMethodService
if (touching != null && touching.word.length() > 1) {
ic.beginBatchEdit();
- if (!applyVoiceAlternatives(touching) && !applyTypedAlternatives(touching)) {
+ if (!mVoiceConnector.applyVoiceAlternatives(touching)
+ && !applyTypedAlternatives(touching)) {
abortCorrection(true);
} else {
TextEntryState.selectedForCorrection();
@@ -2110,14 +1746,15 @@ public class LatinIME extends InputMethodService
ic.endBatchEdit();
} else {
abortCorrection(true);
- setNextSuggestions(); // Show the punctuation suggestions list
+ setPunctuationSuggestions(); // Show the punctuation suggestions list
}
} else {
abortCorrection(true);
}
}
- private void setNextSuggestions() {
+ private void setPunctuationSuggestions() {
+ setCandidatesViewShown(isCandidateStripVisible());
setSuggestions(mSuggestPuncList, false, false, false);
}
@@ -2188,7 +1825,7 @@ public class LatinIME extends InputMethodService
if (!mPredicting && length > 0) {
final InputConnection ic = getCurrentInputConnection();
mPredicting = true;
- mJustRevertedSeparator = ic.getTextBeforeCursor(1, 0);
+ mJustReverted = true;
if (deleteChar) ic.deleteSurroundingText(1, 0);
int toDelete = mCommittedLength;
CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0);
@@ -2199,10 +1836,10 @@ public class LatinIME extends InputMethodService
ic.deleteSurroundingText(toDelete, 0);
ic.setComposingText(mComposing, 1);
TextEntryState.backspace();
- postUpdateSuggestions();
+ mHandler.postUpdateSuggestions();
} else {
sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
- mJustRevertedSeparator = null;
+ mJustReverted = false;
}
}
@@ -2220,8 +1857,8 @@ public class LatinIME extends InputMethodService
}
private void sendSpace() {
- sendKeyChar((char)KEYCODE_SPACE);
- updateShiftKeyState(getCurrentInputEditorInfo());
+ sendKeyChar((char)Keyboard.CODE_SPACE);
+ mKeyboardSwitcher.updateShiftState();
//onKey(KEY_SPACE[0], KEY_SPACE);
}
@@ -2229,30 +1866,33 @@ public class LatinIME extends InputMethodService
return mWord.isFirstCharCapitalized();
}
+ // Notify that Language has been changed and toggleLanguage will update KeyboaredID according
+ // to new Language.
+ public void onKeyboardLanguageChanged() {
+ toggleLanguage(true, true);
+ }
+
+ // "reset" and "next" are used only for USE_SPACEBAR_LANGUAGE_SWITCHER.
private void toggleLanguage(boolean reset, boolean next) {
- if (reset) {
- mLanguageSwitcher.reset();
- } else {
- if (next) {
- mLanguageSwitcher.next();
- } else {
- mLanguageSwitcher.prev();
- }
+ if (SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCHER) {
+ mSubtypeSwitcher.toggleLanguage(reset, next);
}
- int currentKeyboardMode = mKeyboardSwitcher.getKeyboardMode();
- reloadKeyboards();
- mKeyboardSwitcher.makeKeyboards(true);
- mKeyboardSwitcher.setKeyboardMode(currentKeyboardMode, 0,
- mEnableVoiceButton && mEnableVoice);
- initSuggest(mLanguageSwitcher.getInputLanguage());
- mLanguageSwitcher.persist();
- updateShiftKeyState(getCurrentInputEditorInfo());
+ // Reload keyboard because the current language has been changed.
+ KeyboardSwitcher switcher = mKeyboardSwitcher;
+ final int mode = switcher.getKeyboardMode();
+ final EditorInfo attribute = getCurrentInputEditorInfo();
+ final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
+ switcher.loadKeyboard(mode, imeOptions, mVoiceConnector.isVoiceButtonEnabled(),
+ mVoiceConnector.isVoiceButtonOnPrimary());
+ initSuggest();
+ switcher.updateShiftState();
}
+ @Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
+ mSubtypeSwitcher.onSharedPreferenceChanged(sharedPreferences, key);
if (PREF_SELECTED_LANGUAGES.equals(key)) {
- mLanguageSwitcher.loadLocales(sharedPreferences);
mRefreshKeyboardRequired = true;
} else if (PREF_RECORRECTION_ENABLED.equals(key)) {
mReCorrectionEnabled = sharedPreferences.getBoolean(PREF_RECORRECTION_ENABLED,
@@ -2260,79 +1900,58 @@ public class LatinIME extends InputMethodService
}
}
+ @Override
public void swipeRight() {
if (LatinKeyboardView.DEBUG_AUTO_PLAY) {
- ClipboardManager cm = ((ClipboardManager)getSystemService(CLIPBOARD_SERVICE));
- CharSequence text = cm.getText();
+ CharSequence text = ((android.text.ClipboardManager)getSystemService(
+ CLIPBOARD_SERVICE)).getText();
if (!TextUtils.isEmpty(text)) {
mKeyboardSwitcher.getInputView().startPlaying(text.toString());
}
}
}
+ @Override
public void swipeLeft() {
}
+ @Override
public void swipeDown() {
handleClose();
}
+ @Override
public void swipeUp() {
- //launchSettings();
}
+ @Override
public void onPress(int primaryCode) {
vibrate();
playKeyClick(primaryCode);
- final boolean distinctMultiTouch = mKeyboardSwitcher.hasDistinctMultitouch();
- if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_SHIFT) {
- mShiftKeyState.onPress();
- handleShift();
- } else if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_MODE_CHANGE) {
- mSymbolKeyState.onPress();
- changeKeyboardMode();
+ KeyboardSwitcher switcher = mKeyboardSwitcher;
+ final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
+ if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
+ switcher.onPressShift();
+ } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
+ switcher.onPressSymbol();
} else {
- mShiftKeyState.onOtherKeyPressed();
- mSymbolKeyState.onOtherKeyPressed();
+ switcher.onOtherKeyPressed();
}
}
+ @Override
public void onRelease(int primaryCode) {
+ KeyboardSwitcher switcher = mKeyboardSwitcher;
// Reset any drag flags in the keyboard
- ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).keyReleased();
- //vibrate();
- final boolean distinctMultiTouch = mKeyboardSwitcher.hasDistinctMultitouch();
- if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_SHIFT) {
- if (mShiftKeyState.isMomentary())
- resetShift();
- mShiftKeyState.onRelease();
- } else if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_MODE_CHANGE) {
- if (mSymbolKeyState.isMomentary())
- changeKeyboardMode();
- mSymbolKeyState.onRelease();
+ switcher.keyReleased();
+ final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
+ if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
+ switcher.onReleaseShift();
+ } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
+ switcher.onReleaseSymbol();
}
}
- private FieldContext makeFieldContext() {
- return new FieldContext(
- getCurrentInputConnection(),
- getCurrentInputEditorInfo(),
- mLanguageSwitcher.getInputLanguage(),
- mLanguageSwitcher.getEnabledLanguages());
- }
-
- private boolean fieldCanDoVoice(FieldContext fieldContext) {
- return !mPasswordText
- && mVoiceInput != null
- && !mVoiceInput.isBlacklistedField(fieldContext);
- }
-
- private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
- return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext)
- && !(attribute != null
- && IME_OPTION_NO_MICROPHONE.equals(attribute.privateImeOptions))
- && SpeechRecognizer.isRecognitionAvailable(this);
- }
// receive ringer mode changes to detect silent mode
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -2365,13 +1984,13 @@ public class LatinIME extends InputMethodService
// FIXME: These should be triggered after auto-repeat logic
int sound = AudioManager.FX_KEYPRESS_STANDARD;
switch (primaryCode) {
- case Keyboard.KEYCODE_DELETE:
+ case Keyboard.CODE_DELETE:
sound = AudioManager.FX_KEYPRESS_DELETE;
break;
- case KEYCODE_ENTER:
+ case Keyboard.CODE_ENTER:
sound = AudioManager.FX_KEYPRESS_RETURN;
break;
- case KEYCODE_SPACE:
+ case Keyboard.CODE_SPACE:
sound = AudioManager.FX_KEYPRESS_SPACEBAR;
break;
}
@@ -2379,12 +1998,13 @@ public class LatinIME extends InputMethodService
}
}
- private void vibrate() {
+ public void vibrate() {
if (!mVibrateOn) {
return;
}
- if (mKeyboardSwitcher.getInputView() != null) {
- mKeyboardSwitcher.getInputView().performHapticFeedback(
+ LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
+ if (inputView != null) {
+ inputView.performHapticFeedback(
HapticFeedbackConstants.KEYBOARD_TAP,
HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
}
@@ -2393,7 +2013,7 @@ public class LatinIME extends InputMethodService
private void checkTutorial(String privateImeOptions) {
if (privateImeOptions == null) return;
if (privateImeOptions.equals("com.android.setupwizard:ShowTutorial")) {
- if (mTutorial == null) startTutorial();
+ if (mTutorial == null) mHandler.startTutorial();
} else if (privateImeOptions.equals("com.android.setupwizard:HideTutorial")) {
if (mTutorial != null) {
if (mTutorial.close()) {
@@ -2403,24 +2023,23 @@ public class LatinIME extends InputMethodService
}
}
- private void startTutorial() {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_TUTORIAL), 500);
- }
-
- /* package */ void tutorialDone() {
+ // Tutorial.TutorialListener
+ @Override
+ public void onTutorialDone() {
+ sendDownUpKeyEvents(-1); // Inform the setupwizard that tutorial is in last bubble
mTutorial = null;
}
- /* package */ void promoteToUserDictionary(String word, int frequency) {
+ public void promoteToUserDictionary(String word, int frequency) {
if (mUserDictionary.isValidWord(word)) return;
mUserDictionary.addWord(word, frequency);
}
- /* package */ WordComposer getCurrentWord() {
+ public WordComposer getCurrentWord() {
return mWord;
}
- /* package */ boolean getPopupOn() {
+ public boolean getPopupOn() {
return mPopupOn;
}
@@ -2438,11 +2057,22 @@ public class LatinIME extends InputMethodService
}
}
- private void updateAutoTextEnabled(Locale systemLocale) {
+ private void updateAutoTextEnabled() {
if (mSuggest == null) return;
- boolean different =
- !systemLocale.getLanguage().equalsIgnoreCase(mInputLocale.substring(0, 2));
- mSuggest.setAutoTextEnabled(!different && mQuickFixes);
+ mSuggest.setAutoTextEnabled(mQuickFixes
+ && SubtypeSwitcher.getInstance().isSystemLanguageSameAsInputLanguage());
+ }
+
+ private void updateSuggestionVisibility(SharedPreferences prefs) {
+ final String suggestionVisiblityStr = prefs.getString(
+ PREF_SHOW_SUGGESTIONS_SETTING, mResources.getString(
+ R.string.prefs_suggestion_visibility_default_value));
+ for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) {
+ if (suggestionVisiblityStr.equals(mResources.getString(visibility))) {
+ mSuggestionVisibility = visibility;
+ break;
+ }
+ }
}
protected void launchSettings() {
@@ -2453,7 +2083,7 @@ public class LatinIME extends InputMethodService
launchSettings(LatinIMEDebugSettings.class);
}
- protected void launchSettings (Class<? extends PreferenceActivity> settingsClass) {
+ protected void launchSettings(Class<? extends PreferenceActivity> settingsClass) {
handleClose();
Intent intent = new Intent();
intent.setClass(LatinIME.this, settingsClass);
@@ -2461,55 +2091,78 @@ public class LatinIME extends InputMethodService
startActivity(intent);
}
- private void loadSettings() {
+ private void loadSettings(EditorInfo attribute) {
// Get the settings preferences
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
- mVibrateOn = sp.getBoolean(PREF_VIBRATE_ON, false);
- mSoundOn = sp.getBoolean(PREF_SOUND_ON, false);
- mPopupOn = sp.getBoolean(PREF_POPUP_ON,
+ final SharedPreferences prefs = mPrefs;
+ Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
+ mVibrateOn = vibrator != null && vibrator.hasVibrator()
+ && prefs.getBoolean(LatinIMESettings.PREF_VIBRATE_ON, false);
+ mSoundOn = prefs.getBoolean(PREF_SOUND_ON, false);
+ mPopupOn = prefs.getBoolean(PREF_POPUP_ON,
mResources.getBoolean(R.bool.default_popup_preview));
- mAutoCap = sp.getBoolean(PREF_AUTO_CAP, true);
- mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true);
- mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false);
- mHasUsedVoiceInputUnsupportedLocale =
- sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false);
-
- // Get the current list of supported locales and check the current locale against that
- // list. We cache this value so as not to check it every time the user starts a voice
- // input. Because this method is called by onStartInputView, this should mean that as
- // long as the locale doesn't change while the user is keeping the IME open, the
- // value should never be stale.
- String supportedLocalesString = SettingsUtil.getSettingsString(
- getContentResolver(),
- SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES,
- DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES);
- ArrayList<String> voiceInputSupportedLocales =
- newArrayList(supportedLocalesString.split("\\s+"));
-
- mLocaleSupportedForVoiceInput = voiceInputSupportedLocales.contains(mInputLocale);
-
- mShowSuggestions = sp.getBoolean(PREF_SHOW_SUGGESTIONS, true);
-
- if (VOICE_INSTALLED) {
- final String voiceMode = sp.getString(PREF_VOICE_MODE,
- getString(R.string.voice_mode_main));
- boolean enableVoice = !voiceMode.equals(getString(R.string.voice_mode_off))
- && mEnableVoiceButton;
- boolean voiceOnPrimary = voiceMode.equals(getString(R.string.voice_mode_main));
- if (mKeyboardSwitcher != null &&
- (enableVoice != mEnableVoice || voiceOnPrimary != mVoiceOnPrimary)) {
- mKeyboardSwitcher.setVoiceMode(enableVoice, voiceOnPrimary);
+ mAutoCap = prefs.getBoolean(PREF_AUTO_CAP, true);
+ mQuickFixes = prefs.getBoolean(PREF_QUICK_FIXES, true);
+
+ mAutoCorrectEnabled = isAutoCorrectEnabled(prefs);
+ mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(prefs);
+ loadAndSetAutoCompletionThreshold(prefs);
+
+ mVoiceConnector.loadSettings(attribute, prefs);
+
+ updateCorrectionMode();
+ updateAutoTextEnabled();
+ updateSuggestionVisibility(prefs);
+ SubtypeSwitcher.getInstance().loadSettings();
+ }
+
+ /**
+ * load Auto completion threshold from SharedPreferences,
+ * and modify mSuggest's threshold.
+ */
+ private void loadAndSetAutoCompletionThreshold(SharedPreferences sp) {
+ // When mSuggest is not initialized, cannnot modify mSuggest's threshold.
+ if (mSuggest == null) return;
+ // When auto completion setting is turned off, the threshold is ignored.
+ if (!isAutoCorrectEnabled(sp)) return;
+
+ final String currentAutoCompletionSetting = sp.getString(PREF_AUTO_COMPLETION_THRESHOLD,
+ mResources.getString(R.string.auto_completion_threshold_mode_value_modest));
+ final String[] autoCompletionThresholdValues = mResources.getStringArray(
+ R.array.auto_complete_threshold_values);
+ // When autoCompletionThreshold is greater than 1.0,
+ // auto completion is virtually turned off.
+ double autoCompletionThreshold = Double.MAX_VALUE;
+ try {
+ final int arrayIndex = Integer.valueOf(currentAutoCompletionSetting);
+ if (arrayIndex >= 0 && arrayIndex < autoCompletionThresholdValues.length) {
+ autoCompletionThreshold = Double.parseDouble(
+ autoCompletionThresholdValues[arrayIndex]);
}
- mEnableVoice = enableVoice;
- mVoiceOnPrimary = voiceOnPrimary;
+ } catch (NumberFormatException e) {
+ // Whenever the threshold settings are correct,
+ // never come here.
+ autoCompletionThreshold = Double.MAX_VALUE;
+ Log.w(TAG, "Cannot load auto completion threshold setting."
+ + " currentAutoCompletionSetting: " + currentAutoCompletionSetting
+ + ", autoCompletionThresholdValues: "
+ + Arrays.toString(autoCompletionThresholdValues));
}
- mAutoCorrectEnabled = sp.getBoolean(PREF_AUTO_COMPLETE,
- mResources.getBoolean(R.bool.enable_autocorrect)) & mShowSuggestions;
- //mBigramSuggestionEnabled = sp.getBoolean(
- // PREF_BIGRAM_SUGGESTIONS, true) & mShowSuggestions;
- updateCorrectionMode();
- updateAutoTextEnabled(mResources.getConfiguration().locale);
- mLanguageSwitcher.loadLocales(sp);
+ // TODO: This should be refactored :
+ // setAutoCompleteThreshold should be called outside of this method.
+ mSuggest.setAutoCompleteThreshold(autoCompletionThreshold);
+ }
+
+ private boolean isAutoCorrectEnabled(SharedPreferences sp) {
+ final String currentAutoCompletionSetting = sp.getString(PREF_AUTO_COMPLETION_THRESHOLD,
+ mResources.getString(R.string.auto_completion_threshold_mode_value_modest));
+ final String autoCompletionOff = mResources.getString(
+ R.string.auto_completion_threshold_mode_value_off);
+ return !currentAutoCompletionSetting.equals(autoCompletionOff);
+ }
+
+ private boolean isBigramSuggestionEnabled(SharedPreferences sp) {
+ // TODO: Define default value instead of 'true'.
+ return sp.getBoolean(PREF_BIGRAM_SUGGESTIONS, true);
}
private void initSuggestPuncList() {
@@ -2537,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) {
@@ -2544,8 +2198,7 @@ public class LatinIME extends InputMethodService
launchSettings();
break;
case POS_METHOD:
- ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE))
- .showInputMethodPicker();
+ mImm.showInputMethodPicker();
break;
}
}
@@ -2561,22 +2214,6 @@ public class LatinIME extends InputMethodService
mOptionsDialog.show();
}
- private void changeKeyboardMode() {
- mKeyboardSwitcher.toggleSymbols();
- if (mCapsLock && mKeyboardSwitcher.isAlphabetMode()) {
- mKeyboardSwitcher.setShiftLocked(mCapsLock);
- }
-
- updateShiftKeyState(getCurrentInputEditorInfo());
- }
-
- public static <E> ArrayList<E> newArrayList(E... elements) {
- int capacity = (elements.length * 110) / 100 + 5;
- ArrayList<E> list = new ArrayList<E>(capacity);
- Collections.addAll(list, elements);
- return list;
- }
-
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
super.dump(fd, fout, args);
@@ -2584,7 +2221,6 @@ public class LatinIME extends InputMethodService
final Printer p = new PrintWriterPrinter(fout);
p.println("LatinIME state :");
p.println(" Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode());
- p.println(" mCapsLock=" + mCapsLock);
p.println(" mComposing=" + mComposing.toString());
p.println(" mPredictionOn=" + mPredictionOn);
p.println(" mCorrectionMode=" + mCorrectionMode);
@@ -2619,4 +2255,9 @@ public class LatinIME extends InputMethodService
public void onAutoCompletionStateChanged(boolean isAutoCompletion) {
mKeyboardSwitcher.onAutoCompletionStateChanged(isAutoCompletion);
}
+
+ @Override
+ public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) {
+ SubtypeSwitcher.getInstance().updateSubtype(subtype);
+ }
}
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 ffff33da2..3aa89dd59 100644
--- a/java/src/com/android/inputmethod/latin/LatinIMESettings.java
+++ b/java/src/com/android/inputmethod/latin/LatinIMESettings.java
@@ -16,8 +16,8 @@
package com.android.inputmethod.latin;
-import java.util.ArrayList;
-import java.util.Locale;
+import com.android.inputmethod.voice.VoiceIMEConnector;
+import com.android.inputmethod.voice.VoiceInputLogger;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -25,16 +25,19 @@ import android.app.backup.BackupManager;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
+import android.os.Vibrator;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceGroup;
import android.speech.SpeechRecognizer;
import android.text.AutoText;
+import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
import android.util.Log;
+import android.widget.TextView;
-import com.android.inputmethod.voice.SettingsUtil;
-import com.android.inputmethod.voice.VoiceInputLogger;
+import java.util.Locale;
public class LatinIMESettings extends PreferenceActivity
implements SharedPreferences.OnSharedPreferenceChangeListener,
@@ -43,7 +46,10 @@ public class LatinIMESettings extends PreferenceActivity
private static final String QUICK_FIXES_KEY = "quick_fixes";
private static final String PREDICTION_SETTINGS_KEY = "prediction_settings";
private static final String VOICE_SETTINGS_KEY = "voice_mode";
- /* package */ static final String PREF_SETTINGS_KEY = "settings_key";
+ private static final String PREF_AUTO_COMPLETION_THRESHOLD = "auto_completion_threshold";
+ private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
+ public static final String PREF_SETTINGS_KEY = "settings_key";
+ /* package */ static final String PREF_VIBRATE_ON = "vibrate_on";
private static final String TAG = "LatinIMESettings";
@@ -53,13 +59,23 @@ public class LatinIMESettings extends PreferenceActivity
private CheckBoxPreference mQuickFixes;
private ListPreference mVoicePreference;
private ListPreference mSettingsKeyPreference;
+ private ListPreference mAutoCompletionThreshold;
+ private CheckBoxPreference mBigramSuggestion;
private boolean mVoiceOn;
+ private AlertDialog mDialog;
+
private VoiceInputLogger mLogger;
private boolean mOkClicked = false;
private String mVoiceModeOff;
+ private void ensureConsistencyOfAutoCompletionSettings() {
+ final String autoCompletionOff = getResources().getString(
+ R.string.auto_completion_threshold_mode_value_off);
+ final String currentSetting = mAutoCompletionThreshold.getValue();
+ mBigramSuggestion.setEnabled(!currentSetting.equals(autoCompletionOff));
+ }
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -73,6 +89,28 @@ public class LatinIMESettings extends PreferenceActivity
mVoiceModeOff = getString(R.string.voice_mode_off);
mVoiceOn = !(prefs.getString(VOICE_SETTINGS_KEY, mVoiceModeOff).equals(mVoiceModeOff));
mLogger = VoiceInputLogger.getLogger(this);
+
+ mAutoCompletionThreshold = (ListPreference) findPreference(PREF_AUTO_COMPLETION_THRESHOLD);
+ mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS);
+ ensureConsistencyOfAutoCompletionSettings();
+
+ final boolean showSettingsKeyOption = getResources().getBoolean(
+ R.bool.config_enable_show_settings_key_option);
+ if (!showSettingsKeyOption) {
+ getPreferenceScreen().removePreference(mSettingsKeyPreference);
+ }
+
+ final boolean showVoiceKeyOption = getResources().getBoolean(
+ R.bool.config_enable_show_voice_key_option);
+ if (!showVoiceKeyOption) {
+ getPreferenceScreen().removePreference(mVoicePreference);
+ }
+
+ Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
+ if (vibrator == null || !vibrator.hasVibrator()) {
+ getPreferenceScreen().removePreference(
+ getPreferenceScreen().findPreference(PREF_VIBRATE_ON));
+ }
}
@Override
@@ -83,7 +121,7 @@ public class LatinIMESettings extends PreferenceActivity
((PreferenceGroup) findPreference(PREDICTION_SETTINGS_KEY))
.removePreference(mQuickFixes);
}
- if (!LatinIME.VOICE_INSTALLED
+ if (!VoiceIMEConnector.VOICE_INSTALLED
|| !SpeechRecognizer.isRecognitionAvailable(this)) {
getPreferenceScreen().removePreference(mVoicePreference);
} else {
@@ -99,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
@@ -108,6 +147,7 @@ public class LatinIMESettings extends PreferenceActivity
showVoiceConfirmation();
}
}
+ ensureConsistencyOfAutoCompletionSettings();
mVoiceOn = !(prefs.getString(VOICE_SETTINGS_KEY, mVoiceModeOff).equals(mVoiceModeOff));
updateVoiceModeSummary();
updateSettingsKeySummary();
@@ -122,6 +162,13 @@ public class LatinIMESettings extends PreferenceActivity
private void showVoiceConfirmation() {
mOkClicked = false;
showDialog(VOICE_INPUT_CONFIRM_DIALOG);
+ // Make URL in the dialog message clickable
+ if (mDialog != null) {
+ TextView textView = (TextView) mDialog.findViewById(android.R.id.message);
+ if (textView != null) {
+ textView.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+ }
}
private void updateVoiceModeSummary() {
@@ -135,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);
@@ -154,27 +202,23 @@ public class LatinIMESettings extends PreferenceActivity
// Get the current list of supported locales and check the current locale against
// that list, to decide whether to put a warning that voice input will not work in
// the current language as part of the pop-up confirmation dialog.
- String supportedLocalesString = SettingsUtil.getSettingsString(
- getContentResolver(),
- SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES,
- LatinIME.DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES);
- ArrayList<String> voiceInputSupportedLocales =
- LatinIME.newArrayList(supportedLocalesString.split("\\s+"));
- boolean localeSupported = voiceInputSupportedLocales.contains(
+ boolean localeSupported = SubtypeSwitcher.getInstance().isVoiceSupported(
Locale.getDefault().toString());
+ final CharSequence message;
if (localeSupported) {
- String message = getString(R.string.voice_warning_may_not_understand) + "\n\n" +
- getString(R.string.voice_hint_dialog_message);
- builder.setMessage(message);
+ message = TextUtils.concat(
+ getText(R.string.voice_warning_may_not_understand), "\n\n",
+ getText(R.string.voice_hint_dialog_message));
} else {
- String message = getString(R.string.voice_warning_locale_not_supported) +
- "\n\n" + getString(R.string.voice_warning_may_not_understand) + "\n\n" +
- getString(R.string.voice_hint_dialog_message);
- builder.setMessage(message);
+ message = TextUtils.concat(
+ getText(R.string.voice_warning_locale_not_supported), "\n\n",
+ getText(R.string.voice_warning_may_not_understand), "\n\n",
+ getText(R.string.voice_hint_dialog_message));
}
-
+ builder.setMessage(message);
AlertDialog dialog = builder.create();
+ mDialog = dialog;
dialog.setOnDismissListener(this);
mLogger.settingsWarningDialogShown();
return dialog;
@@ -184,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 85ecaee50..f508b9ab8 100644
--- a/java/src/com/android/inputmethod/latin/LatinIMEUtil.java
+++ b/java/src/com/android/inputmethod/latin/LatinIMEUtil.java
@@ -16,12 +16,24 @@
package com.android.inputmethod.latin;
-import android.view.inputmethod.InputMethodManager;
-
-import android.content.Context;
+import android.inputmethodservice.InputMethodService;
import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
import android.text.format.DateUtils;
import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
public class LatinIMEUtil {
@@ -76,9 +88,11 @@ public class LatinIMEUtil {
}
}
- public static boolean hasMultipleEnabledIMEs(Context context) {
- return ((InputMethodManager) context.getSystemService(
- Context.INPUT_METHOD_SERVICE)).getEnabledInputMethodList().size() > 1;
+ public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm) {
+ return imm.getEnabledInputMethodList().size() > 1
+ // imm.getEnabledInputMethodSubtypeList(null) will return the current IME's enabled input
+ // method subtype (The current IME should be LatinIME.)
+ || imm.getEnabledInputMethodSubtypeList(null).size() > 1;
}
/* package */ static class RingCharBuffer {
@@ -86,8 +100,9 @@ public class LatinIMEUtil {
private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
private static final int INVALID_COORDINATE = -2;
/* package */ static final int BUFSIZE = 20;
- private Context mContext;
+ private InputMethodService mContext;
private boolean mEnabled = false;
+ private boolean mUsabilityStudy = false;
private int mEnd = 0;
/* package */ int mLength = 0;
private char[] mCharBuf = new char[BUFSIZE];
@@ -99,9 +114,12 @@ public class LatinIMEUtil {
public static RingCharBuffer getInstance() {
return sRingCharBuffer;
}
- public static RingCharBuffer init(Context context, boolean enabled) {
+ public static RingCharBuffer init(InputMethodService context, boolean enabled,
+ boolean usabilityStudy) {
sRingCharBuffer.mContext = context;
- sRingCharBuffer.mEnabled = enabled;
+ sRingCharBuffer.mEnabled = enabled || usabilityStudy;
+ sRingCharBuffer.mUsabilityStudy = usabilityStudy;
+ UsabilityStudyLogUtils.getInstance().init(context);
return sRingCharBuffer;
}
private int normalize(int in) {
@@ -110,6 +128,9 @@ public class LatinIMEUtil {
}
public void push(char c, int x, int y) {
if (!mEnabled) return;
+ if (mUsabilityStudy) {
+ UsabilityStudyLogUtils.getInstance().writeChar(c, x, y);
+ }
mCharBuf[mEnd] = c;
mXBuf[mEnd] = x;
mYBuf[mEnd] = y;
@@ -153,7 +174,7 @@ public class LatinIMEUtil {
}
}
public String getLastString() {
- StringBuffer sb = new StringBuffer();
+ StringBuilder sb = new StringBuilder();
for (int i = 0; i < mLength; ++i) {
char c = mCharBuf[normalize(mEnd - 1 - i)];
if (!((LatinIME)mContext).isWordSeparator(c)) {
@@ -168,4 +189,205 @@ public class LatinIMEUtil {
mLength = 0;
}
}
+
+ public static int editDistance(CharSequence s, CharSequence t) {
+ if (s == null || t == null) {
+ throw new IllegalArgumentException("editDistance: Arguments should not be null.");
+ }
+ final int sl = s.length();
+ final int tl = t.length();
+ int[][] dp = new int [sl + 1][tl + 1];
+ for (int i = 0; i <= sl; i++) {
+ dp[i][0] = i;
+ }
+ for (int j = 0; j <= tl; j++) {
+ dp[0][j] = j;
+ }
+ for (int i = 0; i < sl; ++i) {
+ for (int j = 0; j < tl; ++j) {
+ if (s.charAt(i) == t.charAt(j)) {
+ dp[i + 1][j + 1] = dp[i][j];
+ } else {
+ dp[i + 1][j + 1] = 1 + Math.min(dp[i][j],
+ Math.min(dp[i + 1][j], dp[i][j + 1]));
+ }
+ }
+ }
+ return dp[sl][tl];
+ }
+
+ // In dictionary.cpp, getSuggestion() method,
+ // suggestion scores are computed using the below formula.
+ // original score (called 'frequency')
+ // := pow(mTypedLetterMultiplier (this is defined 2),
+ // (the number of matched characters between typed word and suggested word))
+ // * (individual word's score which defined in the unigram dictionary,
+ // and this score is defined in range [0, 255].)
+ // * (when before.length() == after.length(),
+ // mFullWordMultiplier (this is defined 2))
+ // So, maximum original score is pow(2, before.length()) * 255 * 2
+ // So, we can normalize original score by dividing this value.
+ private static final int MAX_INITIAL_SCORE = 255;
+ private static final int TYPED_LETTER_MULTIPLIER = 2;
+ private static final int FULL_WORD_MULTIPLYER = 2;
+ public static double calcNormalizedScore(CharSequence before, CharSequence after, int score) {
+ final int beforeLength = before.length();
+ final int afterLength = after.length();
+ final int distance = editDistance(before, after);
+ final double maximumScore = MAX_INITIAL_SCORE
+ * Math.pow(TYPED_LETTER_MULTIPLIER, beforeLength)
+ * FULL_WORD_MULTIPLYER;
+ // add a weight based on edit distance.
+ // distance <= max(afterLength, beforeLength) == afterLength,
+ // so, 0 <= distance / afterLength <= 1
+ final double weight = 1.0 - (double) distance / afterLength;
+ return (score / maximumScore) * weight;
+ }
+
+ public static class UsabilityStudyLogUtils {
+ private static final String TAG = "UsabilityStudyLogUtils";
+ private static final String FILENAME = "log.txt";
+ private static final UsabilityStudyLogUtils sInstance =
+ new UsabilityStudyLogUtils();
+ private final Handler mLoggingHandler;
+ private File mFile;
+ private File mDirectory;
+ private InputMethodService mIms;
+ private PrintWriter mWriter;
+ private final Date mDate;
+ private final SimpleDateFormat mDateFormat;
+
+ private UsabilityStudyLogUtils() {
+ mDate = new Date();
+ mDateFormat = new SimpleDateFormat("dd MMM HH:mm:ss.SSS");
+
+ HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ handlerThread.start();
+ mLoggingHandler = new Handler(handlerThread.getLooper());
+ }
+
+ public static UsabilityStudyLogUtils getInstance() {
+ return sInstance;
+ }
+
+ public void init(InputMethodService ims) {
+ mIms = ims;
+ mDirectory = ims.getFilesDir();
+ }
+
+ private void createLogFileIfNotExist() {
+ if ((mFile == null || !mFile.exists())
+ && (mDirectory != null && mDirectory.exists())) {
+ try {
+ mWriter = getPrintWriter(mDirectory, FILENAME, false);
+ } catch (IOException e) {
+ Log.e(TAG, "Can't create log file.");
+ }
+ }
+ }
+
+ public void writeBackSpace() {
+ UsabilityStudyLogUtils.getInstance().write("<backspace>\t0\t0");
+ }
+
+ public void writeChar(char c, int x, int y) {
+ String inputChar = String.valueOf(c);
+ switch (c) {
+ case '\n':
+ inputChar = "<enter>";
+ break;
+ case '\t':
+ inputChar = "<tab>";
+ break;
+ case ' ':
+ inputChar = "<space>";
+ break;
+ }
+ UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y);
+ LatinImeLogger.onPrintAllUsabilityStudtyLogs();
+ }
+
+ public void write(final String log) {
+ mLoggingHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ createLogFileIfNotExist();
+ final long currentTime = System.currentTimeMillis();
+ mDate.setTime(currentTime);
+
+ final String printString = String.format("%s\t%d\t%s\n",
+ mDateFormat.format(mDate), currentTime, log);
+ if (LatinImeLogger.sDBG) {
+ Log.d(TAG, "Write: " + log);
+ }
+ mWriter.print(printString);
+ }
+ });
+ }
+
+ public void printAll() {
+ mLoggingHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mWriter.flush();
+ StringBuilder sb = new StringBuilder();
+ BufferedReader br = getBufferedReader();
+ String line;
+ try {
+ while ((line = br.readLine()) != null) {
+ sb.append('\n');
+ sb.append(line);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Can't read log file.");
+ } finally {
+ if (LatinImeLogger.sDBG) {
+ Log.d(TAG, "output all logs\n" + sb.toString());
+ }
+ mIms.getCurrentInputConnection().commitText(sb.toString(), 0);
+ try {
+ br.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ });
+ }
+
+ public void clearAll() {
+ mLoggingHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mFile != null && mFile.exists()) {
+ if (LatinImeLogger.sDBG) {
+ Log.d(TAG, "Delete log file.");
+ }
+ mFile.delete();
+ mWriter.close();
+ }
+ }
+ });
+ }
+
+ private BufferedReader getBufferedReader() {
+ createLogFileIfNotExist();
+ try {
+ return new BufferedReader(new FileReader(mFile));
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+
+ private PrintWriter getPrintWriter(
+ File dir, String filename, boolean renew) throws IOException {
+ mFile = new File(dir, filename);
+ if (mFile.exists()) {
+ if (renew) {
+ mFile.delete();
+ }
+ }
+ return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */);
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index a8ab9cc98..de194d21b 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -16,19 +16,23 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.Dictionary.DataType;
import android.content.Context;
import android.content.SharedPreferences;
-import android.inputmethodservice.Keyboard;
+
import java.util.List;
public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
+ public static boolean sDBG = false;
+
+ @Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
}
- public static void init(Context context) {
+ public static void init(Context context, SharedPreferences prefs) {
}
public static void commit() {
@@ -68,4 +72,6 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang
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 45a4a9508..000000000
--- a/java/src/com/android/inputmethod/latin/LatinKeyboard.java
+++ /dev/null
@@ -1,1022 +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.inputmethodservice.Keyboard;
-import android.text.TextPaint;
-import android.util.Log;
-import android.view.ViewConfiguration;
-import android.view.inputmethod.EditorInfo;
-
-import java.util.List;
-import java.util.Locale;
-
-public class LatinKeyboard extends Keyboard {
-
- 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 mShiftLockIcon;
- private Drawable mShiftLockPreviewIcon;
- private Drawable mOldShiftIcon;
- private Drawable mSpaceIcon;
- private Drawable mSpaceAutoCompletionIndicator;
- private Drawable mSpacePreviewIcon;
- private Drawable mMicIcon;
- private Drawable mMicPreviewIcon;
- private Drawable m123MicIcon;
- private Drawable m123MicPreviewIcon;
- private final Drawable mButtonArrowLeftIcon;
- private final Drawable mButtonArrowRightIcon;
- private Key mShiftKey;
- private Key mEnterKey;
- private Key mF1Key;
- private final Drawable mHintIcon;
- private Key mSpaceKey;
- private Key m123Key;
- private final int NUMBER_HINT_COUNT = 10;
- private Key[] mNumberHintKeys;
- private Drawable[] mNumberHintIcons = new Drawable[NUMBER_HINT_COUNT];
- private int mSpaceKeyIndex = -1;
- private int mSpaceDragStartX;
- private int mSpaceDragLastDiff;
- private Locale mLocale;
- private LanguageSwitcher mLanguageSwitcher;
- private final Resources mRes;
- private final Context mContext;
- private int mMode;
- // Whether this keyboard has voice icon on it
- private boolean mHasVoiceButton;
- // Whether voice icon is enabled at all
- private boolean mVoiceEnabled;
- private final boolean mIsAlphaKeyboard;
- private CharSequence m123Label;
- private boolean mCurrentlyInSpace;
- private SlidingLocaleDrawable mSlidingLocaleIcon;
- private int[] mPrefLetterFrequencies;
- private int mPrefLetter;
- private int mPrefLetterX;
- private int mPrefLetterY;
- private int mPrefDistance;
-
- // TODO: generalize for any keyboardId
- private boolean mIsBlackSym;
-
- // TODO: remove this attribute when either Keyboard.mDefaultVerticalGap or Key.parent becomes
- // non-private.
- private final int mVerticalGap;
-
- private static final int SHIFT_OFF = 0;
- private static final int SHIFT_ON = 1;
- private static final int SHIFT_LOCKED = 2;
-
- private int mShiftState = SHIFT_OFF;
-
- 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, int xmlLayoutResId) {
- this(context, xmlLayoutResId, 0);
- }
-
- public LatinKeyboard(Context context, int xmlLayoutResId, int mode) {
- super(context, xmlLayoutResId, mode);
- final Resources res = context.getResources();
- mContext = context;
- mMode = mode;
- mRes = res;
- mShiftLockIcon = res.getDrawable(R.drawable.sym_keyboard_shift_locked);
- mShiftLockPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_shift_locked);
- setDefaultBounds(mShiftLockPreviewIcon);
- mSpaceIcon = res.getDrawable(R.drawable.sym_keyboard_space);
- mSpaceAutoCompletionIndicator = res.getDrawable(R.drawable.sym_keyboard_space_led);
- mSpacePreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_space);
- mMicIcon = res.getDrawable(R.drawable.sym_keyboard_mic);
- mMicPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_mic);
- setDefaultBounds(mMicPreviewIcon);
- mButtonArrowLeftIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_left);
- mButtonArrowRightIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_right);
- m123MicIcon = res.getDrawable(R.drawable.sym_keyboard_123_mic);
- m123MicPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_123_mic);
- mHintIcon = res.getDrawable(R.drawable.hint_popup);
- setDefaultBounds(m123MicPreviewIcon);
- sSpacebarVerticalCorrection = res.getDimensionPixelOffset(
- R.dimen.spacebar_vertical_correction);
- mIsAlphaKeyboard = xmlLayoutResId == R.xml.kbd_qwerty
- || xmlLayoutResId == R.xml.kbd_qwerty_black;
- mSpaceKeyIndex = indexOf(LatinIME.KEYCODE_SPACE);
- initializeNumberHintResources(context);
- // TODO remove this initialization after cleanup
- mVerticalGap = super.getVerticalGap();
- }
-
- private void initializeNumberHintResources(Context context) {
- final Resources res = context.getResources();
- mNumberHintIcons[0] = res.getDrawable(R.drawable.keyboard_hint_0);
- mNumberHintIcons[1] = res.getDrawable(R.drawable.keyboard_hint_1);
- mNumberHintIcons[2] = res.getDrawable(R.drawable.keyboard_hint_2);
- mNumberHintIcons[3] = res.getDrawable(R.drawable.keyboard_hint_3);
- mNumberHintIcons[4] = res.getDrawable(R.drawable.keyboard_hint_4);
- mNumberHintIcons[5] = res.getDrawable(R.drawable.keyboard_hint_5);
- mNumberHintIcons[6] = res.getDrawable(R.drawable.keyboard_hint_6);
- mNumberHintIcons[7] = res.getDrawable(R.drawable.keyboard_hint_7);
- mNumberHintIcons[8] = res.getDrawable(R.drawable.keyboard_hint_8);
- mNumberHintIcons[9] = res.getDrawable(R.drawable.keyboard_hint_9);
- }
-
- @Override
- protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
- XmlResourceParser parser) {
- Key key = new LatinKey(res, parent, x, y, parser);
- switch (key.codes[0]) {
- case LatinIME.KEYCODE_ENTER:
- mEnterKey = key;
- break;
- case LatinKeyboardView.KEYCODE_F1:
- mF1Key = key;
- break;
- case LatinIME.KEYCODE_SPACE:
- mSpaceKey = key;
- break;
- case KEYCODE_MODE_CHANGE:
- m123Key = key;
- m123Label = key.label;
- break;
- }
-
- // For number hints on the upper-right corner of key
- if (mNumberHintKeys == null) {
- // NOTE: This protected method is being called from the base class constructor before
- // mNumberHintKeys gets initialized.
- mNumberHintKeys = new Key[NUMBER_HINT_COUNT];
- }
- int hintNumber = -1;
- if (LatinKeyboardBaseView.isNumberAtLeftmostPopupChar(key)) {
- hintNumber = key.popupCharacters.charAt(0) - '0';
- } else if (LatinKeyboardBaseView.isNumberAtRightmostPopupChar(key)) {
- hintNumber = key.popupCharacters.charAt(key.popupCharacters.length() - 1) - '0';
- }
- if (hintNumber >= 0 && hintNumber <= 9) {
- mNumberHintKeys[hintNumber] = key;
- }
-
- return key;
- }
-
- void setImeOptions(Resources res, int mode, int options) {
- mMode = mode;
- // TODO should clean up this method
- if (mEnterKey != null) {
- // Reset some of the rarely used attributes.
- mEnterKey.popupCharacters = null;
- mEnterKey.popupResId = 0;
- mEnterKey.text = null;
- switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) {
- case EditorInfo.IME_ACTION_GO:
- mEnterKey.iconPreview = null;
- mEnterKey.icon = null;
- mEnterKey.label = res.getText(R.string.label_go_key);
- break;
- case EditorInfo.IME_ACTION_NEXT:
- mEnterKey.iconPreview = null;
- mEnterKey.icon = null;
- mEnterKey.label = res.getText(R.string.label_next_key);
- break;
- case EditorInfo.IME_ACTION_DONE:
- mEnterKey.iconPreview = null;
- mEnterKey.icon = null;
- mEnterKey.label = res.getText(R.string.label_done_key);
- break;
- case EditorInfo.IME_ACTION_SEARCH:
- mEnterKey.iconPreview = res.getDrawable(
- R.drawable.sym_keyboard_feedback_search);
- mEnterKey.icon = res.getDrawable(mIsBlackSym ?
- R.drawable.sym_bkeyboard_search : R.drawable.sym_keyboard_search);
- mEnterKey.label = null;
- break;
- case EditorInfo.IME_ACTION_SEND:
- mEnterKey.iconPreview = null;
- mEnterKey.icon = null;
- mEnterKey.label = res.getText(R.string.label_send_key);
- break;
- default:
- if (mode == KeyboardSwitcher.MODE_IM) {
- mEnterKey.icon = mHintIcon;
- mEnterKey.iconPreview = null;
- mEnterKey.label = ":-)";
- mEnterKey.text = ":-) ";
- mEnterKey.popupResId = R.xml.popup_smileys;
- } else {
- mEnterKey.iconPreview = res.getDrawable(
- R.drawable.sym_keyboard_feedback_return);
- mEnterKey.icon = res.getDrawable(mIsBlackSym ?
- R.drawable.sym_bkeyboard_return : R.drawable.sym_keyboard_return);
- mEnterKey.label = null;
- }
- break;
- }
- // Set the initial size of the preview icon
- if (mEnterKey.iconPreview != null) {
- setDefaultBounds(mEnterKey.iconPreview);
- }
- }
- }
-
- void enableShiftLock() {
- int index = getShiftKeyIndex();
- if (index >= 0) {
- mShiftKey = getKeys().get(index);
- if (mShiftKey instanceof LatinKey) {
- ((LatinKey)mShiftKey).enableShiftLock();
- }
- mOldShiftIcon = mShiftKey.icon;
- }
- }
-
- void setShiftLocked(boolean shiftLocked) {
- if (mShiftKey != null) {
- if (shiftLocked) {
- mShiftKey.on = true;
- mShiftKey.icon = mShiftLockIcon;
- mShiftState = SHIFT_LOCKED;
- } else {
- mShiftKey.on = false;
- mShiftKey.icon = mShiftLockIcon;
- mShiftState = SHIFT_ON;
- }
- }
- }
-
- boolean isShiftLocked() {
- return mShiftState == SHIFT_LOCKED;
- }
-
- @Override
- public boolean setShifted(boolean shiftState) {
- boolean shiftChanged = false;
- if (mShiftKey != null) {
- if (shiftState == false) {
- shiftChanged = mShiftState != SHIFT_OFF;
- mShiftState = SHIFT_OFF;
- mShiftKey.on = false;
- mShiftKey.icon = mOldShiftIcon;
- } else {
- if (mShiftState == SHIFT_OFF) {
- shiftChanged = mShiftState == SHIFT_OFF;
- mShiftState = SHIFT_ON;
- mShiftKey.icon = mShiftLockIcon;
- }
- }
- } else {
- return super.setShifted(shiftState);
- }
- return shiftChanged;
- }
-
- @Override
- public boolean isShifted() {
- if (mShiftKey != null) {
- return mShiftState != SHIFT_OFF;
- } else {
- return super.isShifted();
- }
- }
-
- /* package */ boolean isAlphaKeyboard() {
- return mIsAlphaKeyboard;
- }
-
- public void setColorOfSymbolIcons(boolean isAutoCompletion, boolean isBlack) {
- mIsBlackSym = isBlack;
- if (isBlack) {
- mShiftLockIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_shift_locked);
- mSpaceIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_space);
- mMicIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_mic);
- m123MicIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_123_mic);
- } else {
- mShiftLockIcon = mRes.getDrawable(R.drawable.sym_keyboard_shift_locked);
- mSpaceIcon = mRes.getDrawable(R.drawable.sym_keyboard_space);
- mMicIcon = mRes.getDrawable(R.drawable.sym_keyboard_mic);
- m123MicIcon = mRes.getDrawable(R.drawable.sym_keyboard_123_mic);
- }
- updateDynamicKeys();
- if (mSpaceKey != null) {
- updateSpaceBarForLocale(isAutoCompletion, isBlack);
- }
- updateNumberHintKeys();
- }
-
- private void setDefaultBounds(Drawable drawable) {
- drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
- }
-
- public void setVoiceMode(boolean hasVoiceButton, boolean hasVoice) {
- mHasVoiceButton = hasVoiceButton;
- mVoiceEnabled = hasVoice;
- updateDynamicKeys();
- }
-
- private void updateDynamicKeys() {
- update123Key();
- updateF1Key();
- }
-
- private void update123Key() {
- // Update KEYCODE_MODE_CHANGE key only on alphabet mode, not on symbol mode.
- if (m123Key != null && mIsAlphaKeyboard) {
- if (mVoiceEnabled && !mHasVoiceButton) {
- m123Key.icon = m123MicIcon;
- m123Key.iconPreview = m123MicPreviewIcon;
- m123Key.label = null;
- } else {
- m123Key.icon = null;
- m123Key.iconPreview = null;
- m123Key.label = m123Label;
- }
- }
- }
-
- private void updateF1Key() {
- // Update KEYCODE_F1 key. Please note that some keyboard layouts have no F1 key.
- if (mF1Key == null)
- return;
-
- if (mIsAlphaKeyboard) {
- if (mMode == KeyboardSwitcher.MODE_URL) {
- setNonMicF1Key(mF1Key, "/", R.xml.popup_slash);
- } else if (mMode == KeyboardSwitcher.MODE_EMAIL) {
- setNonMicF1Key(mF1Key, "@", R.xml.popup_at);
- } else {
- if (mVoiceEnabled && mHasVoiceButton) {
- setMicF1Key(mF1Key);
- } else {
- setNonMicF1Key(mF1Key, ",", R.xml.popup_comma);
- }
- }
- } else { // Symbols keyboard
- if (mVoiceEnabled && mHasVoiceButton) {
- setMicF1Key(mF1Key);
- } else {
- setNonMicF1Key(mF1Key, ",", R.xml.popup_comma);
- }
- }
- }
-
- private void setMicF1Key(Key key) {
- // HACK: draw mMicIcon and mHintIcon at the same time
- final Drawable micWithSettingsHintDrawable = new BitmapDrawable(mRes,
- drawSynthesizedSettingsHintImage(key.width, key.height, mMicIcon, mHintIcon));
-
- key.label = null;
- key.codes = new int[] { LatinKeyboardView.KEYCODE_VOICE };
- key.popupResId = R.xml.popup_mic;
- key.icon = micWithSettingsHintDrawable;
- key.iconPreview = mMicPreviewIcon;
- }
-
- private void setNonMicF1Key(Key key, String label, int popupResId) {
- key.label = label;
- key.codes = new int[] { label.charAt(0) };
- key.popupResId = popupResId;
- key.icon = mHintIcon;
- key.iconPreview = null;
- }
-
- public boolean isF1Key(Key key) {
- return key == mF1Key;
- }
-
- public static boolean hasPuncOrSmileysPopup(Key key) {
- return key.popupResId == R.xml.popup_punctuation || key.popupResId == R.xml.popup_smileys;
- }
-
- /**
- * @return a key which should be invalidated.
- */
- public Key onAutoCompletionStateChanged(boolean isAutoCompletion) {
- updateSpaceBarForLocale(isAutoCompletion, mIsBlackSym);
- return mSpaceKey;
- }
-
- private void updateNumberHintKeys() {
- for (int i = 0; i < mNumberHintKeys.length; ++i) {
- if (mNumberHintKeys[i] != null) {
- mNumberHintKeys[i].icon = mNumberHintIcons[i];
- }
- }
- }
-
- public boolean isLanguageSwitchEnabled() {
- return mLocale != null;
- }
-
- private void updateSpaceBarForLocale(boolean isAutoCompletion, boolean isBlack) {
- // If application locales are explicitly selected.
- if (mLocale != null) {
- mSpaceKey.icon = new BitmapDrawable(mRes,
- drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCompletion, isBlack));
- } else {
- // sym_keyboard_space_led can be shared with Black and White symbol themes.
- if (isAutoCompletion) {
- mSpaceKey.icon = new BitmapDrawable(mRes,
- drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCompletion, isBlack));
- } else {
- mSpaceKey.icon = isBlack ? mRes.getDrawable(R.drawable.sym_bkeyboard_space)
- : mRes.getDrawable(R.drawable.sym_keyboard_space);
- }
- }
- }
-
- // 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();
- }
-
- // Overlay two images: mainIcon and hintIcon.
- private Bitmap drawSynthesizedSettingsHintImage(
- int width, int height, Drawable mainIcon, Drawable hintIcon) {
- if (mainIcon == null || hintIcon == null)
- return null;
- Rect hintIconPadding = new Rect(0, 0, 0, 0);
- hintIcon.getPadding(hintIconPadding);
- final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- final Canvas canvas = new Canvas(buffer);
- canvas.drawColor(mRes.getColor(R.color.latinkeyboard_transparent), PorterDuff.Mode.CLEAR);
-
- // Draw main icon at the center of the key visual
- // Assuming the hintIcon shares the same padding with the key's background drawable
- final int drawableX = (width + hintIconPadding.left - hintIconPadding.right
- - mainIcon.getIntrinsicWidth()) / 2;
- final int drawableY = (height + hintIconPadding.top - hintIconPadding.bottom
- - mainIcon.getIntrinsicHeight()) / 2;
- setDefaultBounds(mainIcon);
- canvas.translate(drawableX, drawableY);
- mainIcon.draw(canvas);
- canvas.translate(-drawableX, -drawableY);
-
- // Draw hint icon fully in the key
- hintIcon.setBounds(0, 0, width, height);
- hintIcon.draw(canvas);
- return buffer;
- }
-
- // 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 = LanguageSwitcher.toTitleCase(locale.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 = LanguageSwitcher.toTitleCase(locale.getLanguage());
- 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, boolean isBlack) {
- 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);
- canvas.drawColor(mRes.getColor(R.color.latinkeyboard_transparent), PorterDuff.Mode.CLEAR);
-
- // If application locales are explicitly selected.
- if (mLocale != null) {
- final Paint paint = new Paint();
- paint.setAlpha(opacity);
- paint.setAntiAlias(true);
- paint.setTextAlign(Align.CENTER);
-
- final boolean allowVariableTextSize = true;
- final String language = layoutSpaceBar(paint, mLanguageSwitcher.getInputLocale(),
- mButtonArrowLeftIcon, mButtonArrowRightIcon, width, height,
- getTextSizeFromTheme(android.R.style.TextAppearance_Small, 14),
- allowVariableTextSize);
-
- // Draw language text with shadow
- final int shadowColor = mRes.getColor(isBlack
- ? R.color.latinkeyboard_bar_language_shadow_black
- : R.color.latinkeyboard_bar_language_shadow_white);
- final float baseline = height * SPACEBAR_LANGUAGE_BASELINE;
- final float descent = paint.descent();
- paint.setColor(shadowColor);
- canvas.drawText(language, width / 2, baseline - descent - 1, paint);
- paint.setColor(mRes.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 (mLanguageSwitcher.getLocaleCount() > 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 || mLanguageSwitcher.getLocaleCount() < 2
- || Math.abs(mSpaceDragLastDiff) < mSpaceKey.width * SPACEBAR_DRAG_THRESHOLD ) {
- return 0; // No change
- }
- return mSpaceDragLastDiff > 0 ? 1 : -1;
- }
-
- public void setLanguageSwitcher(LanguageSwitcher switcher, boolean isAutoCompletion,
- boolean isBlackSym) {
- mLanguageSwitcher = switcher;
- Locale locale = mLanguageSwitcher.getLocaleCount() > 0
- ? mLanguageSwitcher.getInputLocale()
- : null;
- // If the language count is 1 and is the same as the system language, don't show it.
- if (locale != null
- && mLanguageSwitcher.getLocaleCount() == 1
- && mLanguageSwitcher.getSystemLocale().getLanguage()
- .equalsIgnoreCase(locale.getLanguage())) {
- locale = null;
- }
- mLocale = locale;
- setColorOfSymbolIcons(isAutoCompletion, isBlackSym);
- }
-
- boolean isCurrentlyInSpace() {
- return mCurrentlyInSpace;
- }
-
- void setPreferredLetters(int[] frequencies) {
- mPrefLetterFrequencies = frequencies;
- mPrefLetter = 0;
- }
-
- 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.
- */
- 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 (mLanguageSwitcher.getLocaleCount() > 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<Key> 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<Key> 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;
- }
-
- // TODO LatinKey could be static class
- class LatinKey extends Keyboard.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, Keyboard.Row parent, int x, int y,
- XmlResourceParser parser) {
- super(res, parent, x, y, parser);
- 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) {
- // TODO This should be done by parent.isInside(this, x, y)
- // if Key.parent were protected.
- boolean result = LatinKeyboard.this.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();
- }
-
- @Override
- public int squaredDistanceFrom(int x, int y) {
- // We should count vertical gap between rows to calculate the center of this Key.
- final int verticalGap = LatinKeyboard.this.mVerticalGap;
- final int xDist = this.x + width / 2 - x;
- final int yDist = this.y + (height + verticalGap) / 2 - y;
- return xDist * xDist + yDist * yDist;
- }
- }
-
- /**
- * 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.
- */
- 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;
- mTextPaint = new TextPaint();
- mTextPaint.setTextSize(getTextSizeFromTheme(android.R.style.TextAppearance_Medium, 18));
- mTextPaint.setColor(R.color.latinkeyboard_transparent);
- mTextPaint.setTextAlign(Align.CENTER);
- mTextPaint.setAlpha(OPACITY_FULLY_OPAQUE);
- mTextPaint.setAntiAlias(true);
- mMiddleX = (mWidth - mBackground.getIntrinsicWidth()) / 2;
- mLeftDrawable =
- mRes.getDrawable(R.drawable.sym_keyboard_feedback_language_arrows_left);
- mRightDrawable =
- mRes.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();
- }
-
- private String getLanguageName(Locale locale) {
- return LanguageSwitcher.toTitleCase(locale.getDisplayLanguage(locale));
- }
-
- @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) {
- final LanguageSwitcher languageSwitcher = mLanguageSwitcher;
- mCurrentLanguage = getLanguageName(languageSwitcher.getInputLocale());
- mNextLanguage = getLanguageName(languageSwitcher.getNextInputLocale());
- mPrevLanguage = getLanguageName(languageSwitcher.getPrevInputLocale());
- }
- // 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/ModifierKeyState.java b/java/src/com/android/inputmethod/latin/ModifierKeyState.java
deleted file mode 100644
index 097e87abe..000000000
--- a/java/src/com/android/inputmethod/latin/ModifierKeyState.java
+++ /dev/null
@@ -1,42 +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;
-
-class ModifierKeyState {
- private static final int RELEASING = 0;
- private static final int PRESSING = 1;
- private static final int MOMENTARY = 2;
-
- private int mState = RELEASING;
-
- public void onPress() {
- mState = PRESSING;
- }
-
- public void onRelease() {
- mState = RELEASING;
- }
-
- public void onOtherKeyPressed() {
- if (mState == PRESSING)
- mState = MOMENTARY;
- }
-
- public boolean isMomentary() {
- return mState == MOMENTARY;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
new file mode 100644
index 000000000..3ee4eb891
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -0,0 +1,478 @@
+/*
+ * 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.keyboard.Keyboard;
+import com.android.inputmethod.voice.SettingsUtil;
+import com.android.inputmethod.voice.VoiceIMEConnector;
+import com.android.inputmethod.voice.VoiceInput;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+public class SubtypeSwitcher {
+ // This flag indicates if we support language switching by swipe on space bar.
+ // We may or may not draw the current language on space bar regardless of this flag.
+ public static final boolean USE_SPACEBAR_LANGUAGE_SWITCHER = false;
+ private static final boolean DBG = false;
+ private static final String TAG = "SubtypeSwitcher";
+
+ private static final char LOCALE_SEPARATER = '_';
+ private static final String KEYBOARD_MODE = "keyboard";
+ private static final String VOICE_MODE = "voice";
+ private final TextUtils.SimpleStringSplitter mLocaleSplitter =
+ new TextUtils.SimpleStringSplitter(LOCALE_SEPARATER);
+
+ private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
+ private /* final */ LatinIME mService;
+ private /* final */ SharedPreferences mPrefs;
+ private /* final */ InputMethodManager mImm;
+ private /* final */ Resources mResources;
+ private final ArrayList<InputMethodSubtype> mEnabledKeyboardSubtypesOfCurrentInputMethod =
+ new ArrayList<InputMethodSubtype>();
+ private final ArrayList<String> mEnabledLanguagesOfCurrentInputMethod = new ArrayList<String>();
+
+ /*-----------------------------------------------------------*/
+ // Variants which should be changed only by reload functions.
+ private Locale mSystemLocale;
+ private Locale mInputLocale;
+ private String mInputLocaleStr;
+ private String mMode;
+ private List<InputMethodSubtype> mAllEnabledSubtypesOfCurrentInputMethod;
+ private VoiceInput mVoiceInput;
+ private boolean mNeedsToDisplayLanguage;
+ private boolean mIsSystemLanguageSameAsInputLanguage;
+ /*-----------------------------------------------------------*/
+
+ public static SubtypeSwitcher getInstance() {
+ return sInstance;
+ }
+
+ public static void init(LatinIME service, SharedPreferences prefs) {
+ sInstance.mPrefs = prefs;
+ sInstance.resetParams(service);
+ if (USE_SPACEBAR_LANGUAGE_SWITCHER) {
+ sInstance.initLanguageSwitcher(service);
+ }
+
+ sInstance.updateAllParameters();
+ }
+
+ private SubtypeSwitcher() {
+ }
+
+ private void resetParams(LatinIME service) {
+ mService = service;
+ mResources = service.getResources();
+ mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE);
+ mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
+ mEnabledLanguagesOfCurrentInputMethod.clear();
+ mSystemLocale = null;
+ mInputLocale = null;
+ mInputLocaleStr = null;
+ mMode = null;
+ mAllEnabledSubtypesOfCurrentInputMethod = null;
+ // TODO: Voice input should be created here
+ mVoiceInput = null;
+ }
+
+ // Update all parameters stored in SubtypeSwitcher.
+ // Only configuration changed event is allowed to call this because this is heavy.
+ private void updateAllParameters() {
+ mSystemLocale = mResources.getConfiguration().locale;
+ updateSubtype(mImm.getCurrentInputMethodSubtype());
+ updateParametersOnStartInputView();
+ }
+
+ // Update parameters which are changed outside LatinIME. This parameters affect UI so they
+ // should be updated every time onStartInputview.
+ public void updateParametersOnStartInputView() {
+ if (USE_SPACEBAR_LANGUAGE_SWITCHER) {
+ updateForSpaceBarLanguageSwitch();
+ } else {
+ updateEnabledSubtypes();
+ }
+ }
+
+ // Reload enabledSubtypes from the framework.
+ private void updateEnabledSubtypes() {
+ boolean foundCurrentSubtypeBecameDisabled = true;
+ mAllEnabledSubtypesOfCurrentInputMethod = mImm.getEnabledInputMethodSubtypeList(null);
+ mEnabledLanguagesOfCurrentInputMethod.clear();
+ mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
+ for (InputMethodSubtype ims: mAllEnabledSubtypesOfCurrentInputMethod) {
+ final String locale = ims.getLocale();
+ final String mode = ims.getMode();
+ mLocaleSplitter.setString(locale);
+ if (mLocaleSplitter.hasNext()) {
+ mEnabledLanguagesOfCurrentInputMethod.add(mLocaleSplitter.next());
+ }
+ if (locale.equals(mInputLocaleStr) && mode.equals(mMode)) {
+ foundCurrentSubtypeBecameDisabled = false;
+ }
+ if (KEYBOARD_MODE.equals(ims.getMode())) {
+ mEnabledKeyboardSubtypesOfCurrentInputMethod.add(ims);
+ }
+ }
+ mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1
+ && mIsSystemLanguageSameAsInputLanguage);
+ if (foundCurrentSubtypeBecameDisabled) {
+ if (DBG) {
+ Log.w(TAG, "Last subtype was disabled. Update to the current one.");
+ }
+ updateSubtype(mImm.getCurrentInputMethodSubtype());
+ }
+ }
+
+ // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
+ public void updateSubtype(InputMethodSubtype newSubtype) {
+ final String newLocale;
+ final String newMode;
+ if (newSubtype == null) {
+ // Normally, newSubtype shouldn't be null. But just in case newSubtype was null,
+ // fallback to the default locale and mode.
+ Log.w(TAG, "Couldn't get the current subtype.");
+ newLocale = "en_US";
+ newMode =KEYBOARD_MODE;
+ } else {
+ newLocale = newSubtype.getLocale();
+ newMode = newSubtype.getMode();
+ }
+ if (DBG) {
+ Log.w(TAG, "Update subtype to:" + newLocale + "," + newMode
+ + ", from: " + mInputLocaleStr + ", " + mMode);
+ }
+ boolean languageChanged = false;
+ if (!newLocale.equals(mInputLocaleStr)) {
+ if (mInputLocaleStr != null) {
+ languageChanged = true;
+ }
+ updateInputLocale(newLocale);
+ }
+ boolean modeChanged = false;
+ String oldMode = mMode;
+ if (!newMode.equals(mMode)) {
+ if (mMode != null) {
+ modeChanged = true;
+ }
+ mMode = newMode;
+ }
+ if (isKeyboardMode()) {
+ if (modeChanged) {
+ if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) {
+ mVoiceInput.cancel();
+ }
+ }
+ if (languageChanged) {
+ mService.onKeyboardLanguageChanged();
+ }
+ } else if (isVoiceMode()) {
+ // If needsToShowWarningDialog is true, voice input need to show warning before
+ // show recognition view.
+ if (languageChanged || modeChanged
+ || VoiceIMEConnector.getInstance().needsToShowWarningDialog()) {
+ if (mVoiceInput != null) {
+ // TODO: Call proper function to trigger VoiceIME
+ mService.onKey(Keyboard.CODE_VOICE, null, 0, 0);
+ }
+ }
+ } else {
+ Log.w(TAG, "Unknown subtype mode: " + mMode);
+ }
+ }
+
+ // Update the current input locale from Locale string.
+ private void updateInputLocale(String inputLocaleStr) {
+ // example: inputLocaleStr = "en_US" "en" ""
+ // "en_US" --> language: en & country: US
+ // "en" --> language: en
+ // "" --> the system locale
+ mLocaleSplitter.setString(inputLocaleStr);
+ if (mLocaleSplitter.hasNext()) {
+ String language = mLocaleSplitter.next();
+ if (mLocaleSplitter.hasNext()) {
+ mInputLocale = new Locale(language, mLocaleSplitter.next());
+ } else {
+ mInputLocale = new Locale(language);
+ }
+ mInputLocaleStr = inputLocaleStr;
+ } else {
+ mInputLocale = mSystemLocale;
+ String country = mSystemLocale.getCountry();
+ mInputLocaleStr = mSystemLocale.getLanguage()
+ + (TextUtils.isEmpty(country) ? "" : "_" + mSystemLocale.getLanguage());
+ }
+ mIsSystemLanguageSameAsInputLanguage = getSystemLocale().getLanguage().equalsIgnoreCase(
+ getInputLocale().getLanguage());
+ mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1
+ && mIsSystemLanguageSameAsInputLanguage);
+ }
+
+ //////////////////////////////////
+ // Language Switching functions //
+ //////////////////////////////////
+
+ public int getEnabledKeyboardLocaleCount() {
+ if (USE_SPACEBAR_LANGUAGE_SWITCHER) {
+ return mLanguageSwitcher.getLocaleCount();
+ } else {
+ return mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
+ }
+ }
+
+ public boolean needsToDisplayLanguage() {
+ return mNeedsToDisplayLanguage;
+ }
+
+ public Locale getInputLocale() {
+ if (USE_SPACEBAR_LANGUAGE_SWITCHER) {
+ return mLanguageSwitcher.getInputLocale();
+ } else {
+ return mInputLocale;
+ }
+ }
+
+ public String getInputLocaleStr() {
+ if (USE_SPACEBAR_LANGUAGE_SWITCHER) {
+ String inputLanguage = null;
+ inputLanguage = mLanguageSwitcher.getInputLanguage();
+ // Should return system locale if there is no Language available.
+ if (inputLanguage == null) {
+ inputLanguage = getSystemLocale().getLanguage();
+ }
+ return inputLanguage;
+ } else {
+ return mInputLocaleStr;
+ }
+ }
+
+ public String[] getEnabledLanguages() {
+ if (USE_SPACEBAR_LANGUAGE_SWITCHER) {
+ return mLanguageSwitcher.getEnabledLanguages();
+ } else {
+ return mEnabledLanguagesOfCurrentInputMethod.toArray(
+ new String[mEnabledLanguagesOfCurrentInputMethod.size()]);
+ }
+ }
+
+ public Locale getSystemLocale() {
+ if (USE_SPACEBAR_LANGUAGE_SWITCHER) {
+ return mLanguageSwitcher.getSystemLocale();
+ } else {
+ return mSystemLocale;
+ }
+ }
+
+ public boolean isSystemLanguageSameAsInputLanguage() {
+ if (USE_SPACEBAR_LANGUAGE_SWITCHER) {
+ return getSystemLocale().getLanguage().equalsIgnoreCase(
+ getInputLocaleStr().substring(0, 2));
+ } else {
+ return mIsSystemLanguageSameAsInputLanguage;
+ }
+ }
+
+ public void onConfigurationChanged(Configuration conf) {
+ final Locale systemLocale = conf.locale;
+ // If system configuration was changed, update all parameters.
+ if (!TextUtils.equals(systemLocale.toString(), mSystemLocale.toString())) {
+ if (USE_SPACEBAR_LANGUAGE_SWITCHER) {
+ // If the system locale changes and is different from the saved
+ // locale (mSystemLocale), then reload the input locale list from the
+ // latin ime settings (shared prefs) and reset the input locale
+ // to the first one.
+ mLanguageSwitcher.loadLocales(mPrefs);
+ mLanguageSwitcher.setSystemLocale(systemLocale);
+ } else {
+ updateAllParameters();
+ }
+ }
+ }
+
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (USE_SPACEBAR_LANGUAGE_SWITCHER) {
+ if (LatinIME.PREF_SELECTED_LANGUAGES.equals(key)) {
+ mLanguageSwitcher.loadLocales(sharedPreferences);
+ }
+ }
+ }
+
+ /**
+ * Change system locale for this application
+ * @param newLocale
+ * @return oldLocale
+ */
+ public Locale changeSystemLocale(Locale newLocale) {
+ Configuration conf = mResources.getConfiguration();
+ Locale oldLocale = conf.locale;
+ conf.locale = newLocale;
+ mResources.updateConfiguration(conf, mResources.getDisplayMetrics());
+ return oldLocale;
+ }
+
+ public boolean isKeyboardMode() {
+ return KEYBOARD_MODE.equals(mMode);
+ }
+
+
+ ///////////////////////////
+ // Voice Input functions //
+ ///////////////////////////
+
+ public boolean setVoiceInput(VoiceInput vi) {
+ if (mVoiceInput == null && vi != null) {
+ mVoiceInput = vi;
+ if (isVoiceMode()) {
+ if (DBG) {
+ Log.d(TAG, "Set and call voice input.");
+ }
+ mService.onKey(Keyboard.CODE_VOICE, null, 0, 0);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean isVoiceMode() {
+ return VOICE_MODE.equals(mMode);
+ }
+
+ //////////////////////////////////////
+ // SpaceBar Language Switch support //
+ //////////////////////////////////////
+
+ private LanguageSwitcher mLanguageSwitcher;
+
+ public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) {
+ if (returnsNameInThisLocale) {
+ return toTitleCase(locale.getDisplayName(locale));
+ } else {
+ return toTitleCase(locale.getDisplayName());
+ }
+ }
+
+ public static String getDisplayLanguage(Locale locale) {
+ return toTitleCase(locale.getDisplayLanguage(locale));
+ }
+
+ public static String getShortDisplayLanguage(Locale locale) {
+ return toTitleCase(locale.getLanguage());
+ }
+
+ private static String toTitleCase(String s) {
+ if (s.length() == 0) {
+ return s;
+ }
+ return Character.toUpperCase(s.charAt(0)) + s.substring(1);
+ }
+
+ private void updateForSpaceBarLanguageSwitch() {
+ // We need to update mNeedsToDisplayLanguage in onStartInputView because
+ // getEnabledKeyboardLocaleCount could have been changed.
+ mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1
+ && getSystemLocale().getLanguage().equalsIgnoreCase(
+ getInputLocale().getLanguage()));
+ }
+
+ public String getInputLanguageName() {
+ return getDisplayLanguage(getInputLocale());
+ }
+
+ public String getNextInputLanguageName() {
+ if (USE_SPACEBAR_LANGUAGE_SWITCHER) {
+ return getDisplayLanguage(mLanguageSwitcher.getNextInputLocale());
+ } else {
+ return "";
+ }
+ }
+
+ public String getPreviousInputLanguageName() {
+ if (USE_SPACEBAR_LANGUAGE_SWITCHER) {
+ return getDisplayLanguage(mLanguageSwitcher.getPrevInputLocale());
+ } else {
+ return "";
+ }
+ }
+
+ // A list of locales which are supported by default for voice input, unless we get a
+ // different list from Gservices.
+ private static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES =
+ "en " +
+ "en_US " +
+ "en_GB " +
+ "en_AU " +
+ "en_CA " +
+ "en_IE " +
+ "en_IN " +
+ "en_NZ " +
+ "en_SG " +
+ "en_ZA ";
+
+ public boolean isVoiceSupported(String locale) {
+ // Get the current list of supported locales and check the current locale against that
+ // list. We cache this value so as not to check it every time the user starts a voice
+ // input. Because this method is called by onStartInputView, this should mean that as
+ // long as the locale doesn't change while the user is keeping the IME open, the
+ // value should never be stale.
+ String supportedLocalesString = SettingsUtil.getSettingsString(
+ mService.getContentResolver(),
+ SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES,
+ DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES);
+ List<String> voiceInputSupportedLocales = Arrays.asList(
+ supportedLocalesString.split("\\s+"));
+ return voiceInputSupportedLocales.contains(locale);
+ }
+
+ public void loadSettings() {
+ if (USE_SPACEBAR_LANGUAGE_SWITCHER) {
+ mLanguageSwitcher.loadLocales(mPrefs);
+ }
+ }
+
+ public void toggleLanguage(boolean reset, boolean next) {
+ if (USE_SPACEBAR_LANGUAGE_SWITCHER) {
+ if (reset) {
+ mLanguageSwitcher.reset();
+ } else {
+ if (next) {
+ mLanguageSwitcher.next();
+ } else {
+ mLanguageSwitcher.prev();
+ }
+ }
+ mLanguageSwitcher.persist(mPrefs);
+ }
+ }
+
+ private void initLanguageSwitcher(LatinIME service) {
+ final Configuration conf = service.getResources().getConfiguration();
+ mLanguageSwitcher = new LanguageSwitcher(service);
+ mLanguageSwitcher.loadLocales(mPrefs);
+ mLanguageSwitcher.setSystemLocale(conf.locale);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 3b898941f..4c9b7509a 100755..100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -16,17 +16,17 @@
package com.android.inputmethod.latin;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
import android.content.Context;
import android.text.AutoText;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
/**
* This class loads a dictionary and provides a list of suggestions for a given sequence of
* characters. This includes corrections and completions.
@@ -81,6 +81,7 @@ public class Suggest implements Dictionary.WordCallback {
private boolean mAutoTextEnabled;
+ private double mAutoCompleteThreshold;
private int[] mPriorities = new int[mPrefMaxSuggestions];
private int[] mBigramPriorities = new int[PREF_MAX_BIGRAMS];
@@ -163,6 +164,10 @@ public class Suggest implements Dictionary.WordCallback {
mUserBigramDictionary = userBigramDictionary;
}
+ public void setAutoCompleteThreshold(double threshold) {
+ mAutoCompleteThreshold = threshold;
+ }
+
/**
* Number of suggestions to generate from the input key sequence. This has
* to be a number between 1 and 100 (inclusive).
@@ -301,8 +306,14 @@ public class Suggest implements Dictionary.WordCallback {
}
mMainDict.getWords(wordComposer, this, mNextLettersFrequencies);
if ((mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM)
- && mSuggestions.size() > 0) {
- mHaveCorrection = true;
+ && mSuggestions.size() > 0 && mPriorities.length > 0) {
+ // TODO: when the normalized score of the first suggestion is nearly equals to
+ // the normalized score of the second suggestion, behave less aggressive.
+ final double normalizedScore = LatinIMEUtil.calcNormalizedScore(
+ mOriginalWord, mSuggestions.get(0), mPriorities[0]);
+ if (normalizedScore >= mAutoCompleteThreshold) {
+ mHaveCorrection = true;
+ }
}
}
if (mOriginalWord != null) {
@@ -326,8 +337,25 @@ public class Suggest implements Dictionary.WordCallback {
String suggestedWord = mSuggestions.get(i).toString().toLowerCase();
CharSequence autoText =
AutoText.get(suggestedWord, 0, suggestedWord.length(), view);
- // Is there an AutoText correction?
+ // Is there an AutoText (also known as Quick Fixes) correction?
boolean canAdd = autoText != null;
+ // Capitalize as needed
+ final int autoTextLength = autoText != null ? autoText.length() : 0;
+ if (autoTextLength > 0 && (mIsAllUpperCase || mIsFirstCharCapitalized)) {
+ int poolSize = mStringPool.size();
+ StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(
+ poolSize - 1) : new StringBuilder(getApproxMaxWordLength());
+ sb.setLength(0);
+ if (mIsAllUpperCase) {
+ sb.append(autoText.toString().toUpperCase());
+ } else if (mIsFirstCharCapitalized) {
+ sb.append(Character.toUpperCase(autoText.charAt(0)));
+ if (autoTextLength > 1) {
+ sb.append(autoText.subSequence(1, autoTextLength));
+ }
+ }
+ autoText = sb.toString();
+ }
// Is that correction already the current prediction (or original word)?
canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i));
// Is that correction already the next predicted word?
@@ -395,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;
@@ -450,8 +479,7 @@ public class Suggest implements Dictionary.WordCallback {
return true;
}
- System.arraycopy(priorities, pos, priorities, pos + 1,
- prefMaxSuggestions - pos - 1);
+ System.arraycopy(priorities, pos, priorities, pos + 1, prefMaxSuggestions - pos - 1);
priorities[pos] = freq;
int poolSize = mStringPool.size();
StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1)
diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java
index 9011191f1..34babdcb5 100644
--- a/java/src/com/android/inputmethod/latin/TextEntryState.java
+++ b/java/src/com/android/inputmethod/latin/TextEntryState.java
@@ -16,8 +16,9 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.Key;
+
import android.content.Context;
-import android.inputmethodservice.Keyboard.Key;
import android.text.format.DateFormat;
import android.util.Log;
@@ -61,7 +62,7 @@ public class TextEntryState {
SPACE_AFTER_PICKED,
UNDO_COMMIT,
CORRECTING,
- PICKED_CORRECTION;
+ PICKED_CORRECTION,
}
private static State sState = State.UNKNOWN;
@@ -96,7 +97,7 @@ public class TextEntryState {
}
try {
sKeyLocationFile.close();
- // Write to log file
+ // Write to log file
// Write timestamp, settings,
String out = DateFormat.format("MM:dd hh:mm:ss", Calendar.getInstance().getTime())
.toString()
@@ -168,6 +169,13 @@ public class TextEntryState {
displayState();
}
+ public static void onAbortCorrection() {
+ if (isCorrecting()) {
+ sState = State.START;
+ }
+ displayState();
+ }
+
public static void typedCharacter(char c, boolean isSeparator) {
boolean isSpace = c == ' ';
switch (sState) {
@@ -253,13 +261,13 @@ public class TextEntryState {
}
public static void keyPressedAt(Key key, int x, int y) {
- if (LOGGING && sKeyLocationFile != null && key.codes[0] >= 32) {
- String out =
- "KEY: " + (char) key.codes[0]
- + " X: " + x
+ if (LOGGING && sKeyLocationFile != null && key.mCodes[0] >= 32) {
+ String out =
+ "KEY: " + (char) key.mCodes[0]
+ + " X: " + x
+ " Y: " + y
- + " MX: " + (key.x + key.width / 2)
- + " MY: " + (key.y + key.height / 2)
+ + " MX: " + (key.mX + key.mWidth / 2)
+ + " MY: " + (key.mY + key.mHeight / 2)
+ "\n";
try {
sKeyLocationFile.write(out.getBytes());
diff --git a/java/src/com/android/inputmethod/latin/Tutorial.java b/java/src/com/android/inputmethod/latin/Tutorial.java
index d3eaf30c6..20767de4d 100644
--- a/java/src/com/android/inputmethod/latin/Tutorial.java
+++ b/java/src/com/android/inputmethod/latin/Tutorial.java
@@ -16,6 +16,9 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.keyboard.LatinKeyboardView;
+
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Handler;
@@ -32,20 +35,24 @@ import android.widget.PopupWindow;
import android.widget.TextView;
import java.util.ArrayList;
-import java.util.List;
public class Tutorial implements OnTouchListener {
-
- private List<Bubble> mBubbles = new ArrayList<Bubble>();
- private View mInputView;
- private LatinIME mIme;
- private int[] mLocation = new int[2];
-
+
+ public interface TutorialListener {
+ public void onTutorialDone();
+ }
+
+ private final ArrayList<Bubble> mBubbles = new ArrayList<Bubble>();
+ private final KeyboardSwitcher mKeyboardSwitcher;
+ private final View mInputView;
+ private final TutorialListener mListener;
+ private final int[] mLocation = new int[2];
+
private static final int MSG_SHOW_BUBBLE = 0;
-
+
private int mBubbleIndex;
-
- Handler mHandler = new Handler() {
+
+ private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -57,20 +64,18 @@ public class Tutorial implements OnTouchListener {
}
};
- class Bubble {
- Drawable bubbleBackground;
- int x;
- int y;
- int width;
- int gravity;
- CharSequence text;
- boolean dismissOnTouch;
- boolean dismissOnClose;
- PopupWindow window;
- TextView textView;
- View inputView;
-
- Bubble(Context context, View inputView,
+ private class Bubble {
+ private final Drawable bubbleBackground;
+ private final int x;
+ private final int y;
+ private final int width;
+ private final int gravity;
+ private final CharSequence text;
+ private final PopupWindow window;
+ private final TextView textView;
+ private final View inputView;
+
+ private Bubble(Context context, View inputView,
int backgroundResource, int bx, int by, int textResource1, int textResource2) {
bubbleBackground = context.getResources().getDrawable(backgroundResource);
x = bx;
@@ -81,8 +86,6 @@ public class Tutorial implements OnTouchListener {
.append(context.getResources().getText(textResource1))
.append("\n")
.append(context.getResources().getText(textResource2));
- this.dismissOnTouch = true;
- this.dismissOnClose = false;
this.inputView = inputView;
window = new PopupWindow(context);
window.setBackgroundDrawable(null);
@@ -124,7 +127,7 @@ public class Tutorial implements OnTouchListener {
return l.getHeight();
}
- void show(int offx, int offy) {
+ private void show(int offx, int offy) {
int textHeight = chooseSize(window, inputView, text, textView);
offy -= textView.getPaddingTop() + textHeight;
if (inputView.getVisibility() == View.VISIBLE
@@ -133,6 +136,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;
@@ -144,63 +148,66 @@ public class Tutorial implements OnTouchListener {
}
}
}
-
- void hide() {
+
+ private void hide() {
if (window.isShowing()) {
textView.setOnTouchListener(null);
window.dismiss();
}
}
-
- boolean isShowing() {
+
+ private boolean isShowing() {
return window.isShowing();
}
}
-
- public Tutorial(LatinIME ime, LatinKeyboardView inputView) {
+
+ public Tutorial(TutorialListener listener, KeyboardSwitcher keyboardSwitcher) {
+ mListener = listener;
+ mKeyboardSwitcher = keyboardSwitcher;
+ LatinKeyboardView inputView = keyboardSwitcher.getInputView();
+ mInputView = inputView;
Context context = inputView.getContext();
- mIme = ime;
int inputWidth = inputView.getWidth();
final int x = inputWidth / 20; // Half of 1/10th
+ ArrayList<Bubble> bubbles = mBubbles;
Bubble bWelcome = new Bubble(context, inputView,
R.drawable.dialog_bubble_step02, x, 0,
R.string.tip_to_open_keyboard, R.string.touch_to_continue);
- mBubbles.add(bWelcome);
+ bubbles.add(bWelcome);
Bubble bAccents = new Bubble(context, inputView,
R.drawable.dialog_bubble_step02, x, 0,
R.string.tip_to_view_accents, R.string.touch_to_continue);
- mBubbles.add(bAccents);
+ bubbles.add(bAccents);
Bubble b123 = new Bubble(context, inputView,
R.drawable.dialog_bubble_step07, x, 0,
R.string.tip_to_open_symbols, R.string.touch_to_continue);
- mBubbles.add(b123);
+ bubbles.add(b123);
Bubble bABC = new Bubble(context, inputView,
R.drawable.dialog_bubble_step07, x, 0,
R.string.tip_to_close_symbols, R.string.touch_to_continue);
- mBubbles.add(bABC);
+ bubbles.add(bABC);
Bubble bSettings = new Bubble(context, inputView,
R.drawable.dialog_bubble_step07, x, 0,
R.string.tip_to_launch_settings, R.string.touch_to_continue);
- mBubbles.add(bSettings);
+ bubbles.add(bSettings);
Bubble bDone = new Bubble(context, inputView,
R.drawable.dialog_bubble_step02, x, 0,
R.string.tip_to_start_typing, R.string.touch_to_finish);
- mBubbles.add(bDone);
- mInputView = inputView;
+ bubbles.add(bDone);
}
-
- void start() {
+
+ public void start() {
mInputView.getLocationInWindow(mLocation);
mBubbleIndex = -1;
mInputView.setOnTouchListener(this);
next();
}
- boolean next() {
+ private void next() {
if (mBubbleIndex >= 0) {
// If the bubble is not yet showing, don't move to the next.
if (!mBubbles.get(mBubbleIndex).isShowing()) {
- return true;
+ return;
}
// Hide all previous bubbles as well, as they may have had a delayed show
for (int i = 0; i <= mBubbleIndex; i++) {
@@ -210,31 +217,31 @@ public class Tutorial implements OnTouchListener {
mBubbleIndex++;
if (mBubbleIndex >= mBubbles.size()) {
mInputView.setOnTouchListener(null);
- mIme.sendDownUpKeyEvents(-1); // Inform the setupwizard that tutorial is in last bubble
- mIme.tutorialDone();
- return false;
+ mListener.onTutorialDone();
+ return;
}
if (mBubbleIndex == 3 || mBubbleIndex == 4) {
- mIme.mKeyboardSwitcher.toggleSymbols();
+ mKeyboardSwitcher.changeKeyboardMode();
}
mHandler.sendMessageDelayed(
mHandler.obtainMessage(MSG_SHOW_BUBBLE, mBubbles.get(mBubbleIndex)), 500);
- return true;
+ return;
}
-
- void hide() {
- for (int i = 0; i < mBubbles.size(); i++) {
- mBubbles.get(i).hide();
+
+ private void hide() {
+ for (Bubble bubble : mBubbles) {
+ bubble.hide();
}
mInputView.setOnTouchListener(null);
}
- boolean close() {
+ public boolean close() {
mHandler.removeMessages(MSG_SHOW_BUBBLE);
hide();
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/UserBigramDictionary.java b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
index 67d9c0bcf..6d2f6b611 100644
--- a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
@@ -16,10 +16,6 @@
package com.android.inputmethod.latin;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -30,6 +26,10 @@ import android.os.AsyncTask;
import android.provider.BaseColumns;
import android.util.Log;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+
/**
* Stores all the pairs user types in databases. Prune the database if the size
* gets too big. Unlike AutoDictionary, it even stores the pairs that are already
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/latin/Hints.java b/java/src/com/android/inputmethod/voice/Hints.java
index c467365e7..d11d3b042 100644
--- a/java/src/com/android/inputmethod/latin/Hints.java
+++ b/java/src/com/android/inputmethod/voice/Hints.java
@@ -14,14 +14,14 @@
* the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.voice;
-import com.android.inputmethod.voice.SettingsUtil;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SharedPreferencesCompat;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
import android.view.inputmethod.InputConnection;
import java.util.Calendar;
@@ -47,8 +47,9 @@ public class Hints {
private static final int DEFAULT_SWIPE_HINT_MAX_DAYS_TO_SHOW = 7;
private static final int DEFAULT_PUNCTUATION_HINT_MAX_DISPLAYS = 7;
- private Context mContext;
- private Display mDisplay;
+ private final Context mContext;
+ private final SharedPreferences mPrefs;
+ private final Display mDisplay;
private boolean mVoiceResultContainedPunctuation;
private int mSwipeHintMaxDaysToShow;
private int mPunctuationHintMaxDisplays;
@@ -62,8 +63,9 @@ public class Hints {
SPEAKABLE_PUNCTUATION.put("?", "question mark");
}
- public Hints(Context context, Display display) {
+ public Hints(Context context, SharedPreferences prefs, Display display) {
mContext = context;
+ mPrefs = prefs;
mDisplay = display;
ContentResolver cr = mContext.getContentResolver();
@@ -103,8 +105,7 @@ public class Hints {
public void registerVoiceResult(String text) {
// Update the current time as the last time voice input was used.
- SharedPreferences.Editor editor =
- PreferenceManager.getDefaultSharedPreferences(mContext).edit();
+ SharedPreferences.Editor editor = mPrefs.edit();
editor.putLong(PREF_VOICE_INPUT_LAST_TIME_USED, System.currentTimeMillis());
SharedPreferencesCompat.apply(editor);
@@ -118,14 +119,14 @@ public class Hints {
}
private boolean shouldShowSwipeHint() {
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+ final SharedPreferences prefs = mPrefs;
- int numUniqueDaysShown = sp.getInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, 0);
+ int numUniqueDaysShown = prefs.getInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, 0);
// If we've already shown the hint for enough days, we'll return false.
if (numUniqueDaysShown < mSwipeHintMaxDaysToShow) {
- long lastTimeVoiceWasUsed = sp.getLong(PREF_VOICE_INPUT_LAST_TIME_USED, 0);
+ long lastTimeVoiceWasUsed = prefs.getLong(PREF_VOICE_INPUT_LAST_TIME_USED, 0);
// If the user has used voice today, we'll return false. (We don't show the hint on
// any day that the user has already used voice.)
@@ -156,16 +157,16 @@ public class Hints {
}
private void showHint(int hintViewResource) {
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+ final SharedPreferences prefs = mPrefs;
- int numUniqueDaysShown = sp.getInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, 0);
- long lastTimeHintWasShown = sp.getLong(PREF_VOICE_HINT_LAST_TIME_SHOWN, 0);
+ int numUniqueDaysShown = prefs.getInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, 0);
+ long lastTimeHintWasShown = prefs.getLong(PREF_VOICE_HINT_LAST_TIME_SHOWN, 0);
// If this is the first time the hint is being shown today, increase the saved values
// to represent that. We don't need to increase the last time the hint was shown unless
// it is a different day from the current value.
if (!isFromToday(lastTimeHintWasShown)) {
- SharedPreferences.Editor editor = sp.edit();
+ SharedPreferences.Editor editor = prefs.edit();
editor.putInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, numUniqueDaysShown + 1);
editor.putLong(PREF_VOICE_HINT_LAST_TIME_SHOWN, System.currentTimeMillis());
SharedPreferencesCompat.apply(editor);
@@ -177,9 +178,9 @@ public class Hints {
}
private int getAndIncrementPref(String pref) {
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
- int value = sp.getInt(pref, 0);
- SharedPreferences.Editor editor = sp.edit();
+ final SharedPreferences prefs = mPrefs;
+ int value = prefs.getInt(pref, 0);
+ SharedPreferences.Editor editor = prefs.edit();
editor.putInt(pref, value + 1);
SharedPreferencesCompat.apply(editor);
return value;
diff --git a/java/src/com/android/inputmethod/voice/RecognitionView.java b/java/src/com/android/inputmethod/voice/RecognitionView.java
index 7cec0b04a..12d0de852 100644
--- a/java/src/com/android/inputmethod/voice/RecognitionView.java
+++ b/java/src/com/android/inputmethod/voice/RecognitionView.java
@@ -16,12 +16,7 @@
package com.android.inputmethod.voice;
-import java.io.ByteArrayOutputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.ShortBuffer;
-import java.util.ArrayList;
-import java.util.List;
+import com.android.inputmethod.latin.R;
import android.content.ContentResolver;
import android.content.Context;
@@ -43,7 +38,12 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
-import com.android.inputmethod.latin.R;
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.List;
/**
* The user interface for the "Speak now" and "working" states.
@@ -51,6 +51,7 @@ import com.android.inputmethod.latin.R;
* 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/SettingsUtil.java b/java/src/com/android/inputmethod/voice/SettingsUtil.java
index abf52047f..4d746e120 100644
--- a/java/src/com/android/inputmethod/voice/SettingsUtil.java
+++ b/java/src/com/android/inputmethod/voice/SettingsUtil.java
@@ -17,10 +17,7 @@
package com.android.inputmethod.voice;
import android.content.ContentResolver;
-import android.database.Cursor;
-import android.net.Uri;
import android.provider.Settings;
-import android.util.Log;
/**
* Utility for retrieving settings from Settings.Secure.
diff --git a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
new file mode 100644
index 000000000..7ad6c35a0
--- /dev/null
+++ b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
@@ -0,0 +1,688 @@
+/*
+ * 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.voice;
+
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.latin.EditingUtil;
+import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.LatinIME.UIHandler;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SharedPreferencesCompat;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.provider.Browser;
+import android.speech.SpeechRecognizer;
+import android.text.Layout;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
+import android.text.style.URLSpan;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class VoiceIMEConnector implements VoiceInput.UiListener {
+ private static final VoiceIMEConnector sInstance = new VoiceIMEConnector();
+
+ public static final boolean VOICE_INSTALLED = true;
+ private static final boolean ENABLE_VOICE_BUTTON = true;
+ private static final String PREF_VOICE_MODE = "voice_mode";
+ // Whether or not the user has used voice input before (and thus, whether to show the
+ // first-run warning dialog or not).
+ private static final String PREF_HAS_USED_VOICE_INPUT = "has_used_voice_input";
+ // Whether or not the user has used voice input from an unsupported locale UI before.
+ // For example, the user has a Chinese UI but activates voice input.
+ private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE =
+ "has_used_voice_input_unsupported_locale";
+ // The private IME option used to indicate that no microphone should be shown for a
+ // given text field. For instance this is specified by the search dialog when the
+ // dialog is already showing a voice search button.
+ private static final String IME_OPTION_NO_MICROPHONE = "nm";
+
+ private boolean mAfterVoiceInput;
+ private boolean mHasUsedVoiceInput;
+ private boolean mHasUsedVoiceInputUnsupportedLocale;
+ private boolean mImmediatelyAfterVoiceInput;
+ private boolean mIsShowingHint;
+ private boolean mLocaleSupportedForVoiceInput;
+ private boolean mPasswordText;
+ private boolean mRecognizing;
+ private boolean mShowingVoiceSuggestions;
+ private boolean mVoiceButtonEnabled;
+ private boolean mVoiceButtonOnPrimary;
+ private boolean mVoiceInputHighlighted;
+
+ private InputMethodManager mImm;
+ private LatinIME mContext;
+ private AlertDialog mVoiceWarningDialog;
+ private VoiceInput mVoiceInput;
+ private final VoiceResults mVoiceResults = new VoiceResults();
+ private Hints mHints;
+ private UIHandler mHandler;
+ private SubtypeSwitcher mSubtypeSwitcher;
+ // For each word, a list of potential replacements, usually from voice.
+ private final Map<String, List<CharSequence>> mWordToSuggestions =
+ new HashMap<String, List<CharSequence>>();
+
+ public static VoiceIMEConnector init(LatinIME context, SharedPreferences prefs, UIHandler h) {
+ sInstance.initInternal(context, prefs, h);
+ return sInstance;
+ }
+
+ public static VoiceIMEConnector getInstance() {
+ return sInstance;
+ }
+
+ private void initInternal(LatinIME context, SharedPreferences prefs, UIHandler h) {
+ mContext = context;
+ mHandler = h;
+ mImm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ mSubtypeSwitcher = SubtypeSwitcher.getInstance();
+ 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);
+ View view = inflater.inflate(viewResource, null);
+ mContext.setCandidatesView(view);
+ mContext.setCandidatesViewShown(true);
+ mIsShowingHint = true;
+ }
+ });
+ }
+ }
+
+ private VoiceIMEConnector() {
+ }
+
+ public void resetVoiceStates(boolean isPasswordText) {
+ mAfterVoiceInput = false;
+ mImmediatelyAfterVoiceInput = false;
+ mShowingVoiceSuggestions = false;
+ mVoiceInputHighlighted = false;
+ mPasswordText = isPasswordText;
+ }
+
+ public void flushVoiceInputLogs(boolean configurationChanged) {
+ if (VOICE_INSTALLED && !configurationChanged) {
+ if (mAfterVoiceInput) {
+ mVoiceInput.flushAllTextModificationCounters();
+ mVoiceInput.logInputEnded();
+ }
+ mVoiceInput.flushLogs();
+ mVoiceInput.cancel();
+ }
+ }
+
+ public void flushAndLogAllTextModificationCounters(int index, CharSequence suggestion,
+ String wordSeparators) {
+ if (mAfterVoiceInput && mShowingVoiceSuggestions) {
+ mVoiceInput.flushAllTextModificationCounters();
+ // send this intent AFTER logging any prior aggregated edits.
+ mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index,
+ wordSeparators, mContext.getCurrentInputConnection());
+ }
+ }
+
+ private void showVoiceWarningDialog(final boolean swipe, IBinder token,
+ final boolean configurationChanging) {
+ if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
+ return;
+ }
+ AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ builder.setCancelable(true);
+ builder.setIcon(R.drawable.ic_mic_dialog);
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ mVoiceInput.logKeyboardWarningDialogOk();
+ reallyStartListening(swipe, configurationChanging);
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ mVoiceInput.logKeyboardWarningDialogCancel();
+ switchToLastInputMethod();
+ }
+ });
+ // When the dialog is dismissed by user's cancellation, switch back to the last input method
+ builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface arg0) {
+ mVoiceInput.logKeyboardWarningDialogCancel();
+ switchToLastInputMethod();
+ }
+ });
+
+ final CharSequence message;
+ if (mLocaleSupportedForVoiceInput) {
+ message = TextUtils.concat(
+ mContext.getText(R.string.voice_warning_may_not_understand), "\n\n",
+ mContext.getText(R.string.voice_warning_how_to_turn_off));
+ } else {
+ message = TextUtils.concat(
+ mContext.getText(R.string.voice_warning_locale_not_supported), "\n\n",
+ mContext.getText(R.string.voice_warning_may_not_understand), "\n\n",
+ mContext.getText(R.string.voice_warning_how_to_turn_off));
+ }
+ builder.setMessage(message);
+
+ builder.setTitle(R.string.voice_warning_title);
+ mVoiceWarningDialog = builder.create();
+ Window window = mVoiceWarningDialog.getWindow();
+ WindowManager.LayoutParams lp = window.getAttributes();
+ lp.token = token;
+ lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+ window.setAttributes(lp);
+ window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ mVoiceInput.logKeyboardWarningDialogShown();
+ mVoiceWarningDialog.show();
+ // Make URL in the dialog message clickable
+ TextView textView = (TextView) mVoiceWarningDialog.findViewById(android.R.id.message);
+ if (textView != null) {
+ final CustomLinkMovementMethod method = CustomLinkMovementMethod.getInstance();
+ method.setVoiceWarningDialog(mVoiceWarningDialog);
+ textView.setMovementMethod(method);
+ }
+ }
+
+ private static class CustomLinkMovementMethod extends LinkMovementMethod {
+ private static CustomLinkMovementMethod sInstance = new CustomLinkMovementMethod();
+ private AlertDialog mAlertDialog;
+
+ public void setVoiceWarningDialog(AlertDialog alertDialog) {
+ mAlertDialog = alertDialog;
+ }
+
+ public static CustomLinkMovementMethod getInstance() {
+ return sInstance;
+ }
+
+ // Almost the same as LinkMovementMethod.onTouchEvent(), but overrides it for
+ // FLAG_ACTIVITY_NEW_TASK and mAlertDialog.cancel().
+ @Override
+ public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
+ int action = event.getAction();
+
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+
+ x -= widget.getTotalPaddingLeft();
+ y -= widget.getTotalPaddingTop();
+
+ x += widget.getScrollX();
+ y += widget.getScrollY();
+
+ Layout layout = widget.getLayout();
+ int line = layout.getLineForVertical(y);
+ int off = layout.getOffsetForHorizontal(line, x);
+
+ ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
+
+ if (link.length != 0) {
+ if (action == MotionEvent.ACTION_UP) {
+ if (link[0] instanceof URLSpan) {
+ URLSpan urlSpan = (URLSpan) link[0];
+ Uri uri = Uri.parse(urlSpan.getURL());
+ Context context = widget.getContext();
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
+ if (mAlertDialog != null) {
+ // Go back to the previous IME for now.
+ // TODO: If we can find a way to bring the new activity to front
+ // while keeping the warning dialog, we don't need to cancel here.
+ mAlertDialog.cancel();
+ }
+ context.startActivity(intent);
+ } else {
+ link[0].onClick(widget);
+ }
+ } else if (action == MotionEvent.ACTION_DOWN) {
+ Selection.setSelection(buffer, buffer.getSpanStart(link[0]),
+ buffer.getSpanEnd(link[0]));
+ }
+ return true;
+ } else {
+ Selection.removeSelection(buffer);
+ }
+ }
+ return super.onTouchEvent(widget, buffer, event);
+ }
+ }
+
+ public void showPunctuationHintIfNecessary() {
+ InputConnection ic = mContext.getCurrentInputConnection();
+ if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) {
+ if (mHints.showPunctuationHintIfNecessary(ic)) {
+ mVoiceInput.logPunctuationHintDisplayed();
+ }
+ }
+ mImmediatelyAfterVoiceInput = false;
+ }
+
+ public void hideVoiceWindow(boolean configurationChanging) {
+ if (!configurationChanging) {
+ if (mAfterVoiceInput)
+ mVoiceInput.logInputEnded();
+ if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
+ mVoiceInput.logKeyboardWarningDialogDismissed();
+ mVoiceWarningDialog.dismiss();
+ mVoiceWarningDialog = null;
+ }
+ if (VOICE_INSTALLED & mRecognizing) {
+ mVoiceInput.cancel();
+ }
+ }
+ mWordToSuggestions.clear();
+ }
+
+ public void setCursorAndSelection(int newSelEnd, int newSelStart) {
+ if (mAfterVoiceInput) {
+ mVoiceInput.setCursorPos(newSelEnd);
+ mVoiceInput.setSelectionSpan(newSelEnd - newSelStart);
+ }
+ }
+
+ public void setVoiceInputHighlighted(boolean b) {
+ mVoiceInputHighlighted = b;
+ }
+
+ public void setShowingVoiceSuggestions(boolean b) {
+ mShowingVoiceSuggestions = b;
+ }
+
+ public boolean isVoiceButtonEnabled() {
+ return mVoiceButtonEnabled;
+ }
+
+ public boolean isVoiceButtonOnPrimary() {
+ return mVoiceButtonOnPrimary;
+ }
+
+ public boolean isVoiceInputHighlighted() {
+ return mVoiceInputHighlighted;
+ }
+
+ public boolean isRecognizing() {
+ return mRecognizing;
+ }
+
+ public boolean needsToShowWarningDialog() {
+ return !mHasUsedVoiceInput
+ || (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale);
+ }
+
+ public boolean getAndResetIsShowingHint() {
+ boolean ret = mIsShowingHint;
+ mIsShowingHint = false;
+ return ret;
+ }
+
+ private void revertVoiceInput() {
+ InputConnection ic = mContext.getCurrentInputConnection();
+ if (ic != null) ic.commitText("", 1);
+ mContext.updateSuggestions();
+ mVoiceInputHighlighted = false;
+ }
+
+ public void commitVoiceInput() {
+ if (VOICE_INSTALLED && mVoiceInputHighlighted) {
+ InputConnection ic = mContext.getCurrentInputConnection();
+ if (ic != null) ic.finishComposingText();
+ mContext.updateSuggestions();
+ mVoiceInputHighlighted = false;
+ }
+ }
+
+ public boolean logAndRevertVoiceInput() {
+ if (VOICE_INSTALLED && mVoiceInputHighlighted) {
+ mVoiceInput.incrementTextModificationDeleteCount(
+ mVoiceResults.candidates.get(0).toString().length());
+ revertVoiceInput();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public void rememberReplacedWord(CharSequence suggestion,String wordSeparators) {
+ if (mShowingVoiceSuggestions) {
+ // Retain the replaced word in the alternatives array.
+ EditingUtil.Range range = new EditingUtil.Range();
+ String wordToBeReplaced = EditingUtil.getWordAtCursor(
+ mContext.getCurrentInputConnection(), wordSeparators, range);
+ if (!mWordToSuggestions.containsKey(wordToBeReplaced)) {
+ wordToBeReplaced = wordToBeReplaced.toLowerCase();
+ }
+ if (mWordToSuggestions.containsKey(wordToBeReplaced)) {
+ List<CharSequence> suggestions = mWordToSuggestions.get(wordToBeReplaced);
+ if (suggestions.contains(suggestion)) {
+ suggestions.remove(suggestion);
+ }
+ suggestions.add(wordToBeReplaced);
+ mWordToSuggestions.remove(wordToBeReplaced);
+ mWordToSuggestions.put(suggestion.toString(), suggestions);
+ }
+ }
+ }
+
+ /**
+ * Tries to apply any voice alternatives for the word if this was a spoken word and
+ * there are voice alternatives.
+ * @param touching The word that the cursor is touching, with position information
+ * @return true if an alternative was found, false otherwise.
+ */
+ public boolean applyVoiceAlternatives(EditingUtil.SelectedWord touching) {
+ // Search for result in spoken word alternatives
+ String selectedWord = touching.word.toString().trim();
+ if (!mWordToSuggestions.containsKey(selectedWord)) {
+ selectedWord = selectedWord.toLowerCase();
+ }
+ if (mWordToSuggestions.containsKey(selectedWord)) {
+ mShowingVoiceSuggestions = true;
+ List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord);
+ // If the first letter of touching is capitalized, make all the suggestions
+ // start with a capital letter.
+ if (Character.isUpperCase(touching.word.charAt(0))) {
+ for (int i = 0; i < suggestions.size(); i++) {
+ String origSugg = (String) suggestions.get(i);
+ String capsSugg = origSugg.toUpperCase().charAt(0)
+ + origSugg.subSequence(1, origSugg.length()).toString();
+ suggestions.set(i, capsSugg);
+ }
+ }
+ mContext.setSuggestions(suggestions, false, true, true);
+ mContext.setCandidatesViewShown(true);
+ return true;
+ }
+ return false;
+ }
+
+ public void handleBackspace() {
+ if (mAfterVoiceInput) {
+ // Don't log delete if the user is pressing delete at
+ // the beginning of the text box (hence not deleting anything)
+ if (mVoiceInput.getCursorPos() > 0) {
+ // If anything was selected before the delete was pressed, increment the
+ // delete count by the length of the selection
+ int deleteLen = mVoiceInput.getSelectionSpan() > 0 ?
+ mVoiceInput.getSelectionSpan() : 1;
+ mVoiceInput.incrementTextModificationDeleteCount(deleteLen);
+ }
+ }
+ }
+
+ public void handleCharacter() {
+ commitVoiceInput();
+ if (mAfterVoiceInput) {
+ // Assume input length is 1. This assumption fails for smiley face insertions.
+ mVoiceInput.incrementTextModificationInsertCount(1);
+ }
+ }
+
+ public void handleSeparator() {
+ commitVoiceInput();
+ if (mAfterVoiceInput){
+ // Assume input length is 1. This assumption fails for smiley face insertions.
+ mVoiceInput.incrementTextModificationInsertPunctuationCount(1);
+ }
+ }
+
+ public void handleClose() {
+ if (VOICE_INSTALLED & mRecognizing) {
+ mVoiceInput.cancel();
+ }
+ }
+
+
+ public void handleVoiceResults(KeyboardSwitcher switcher, boolean capitalizeFirstWord) {
+ mAfterVoiceInput = true;
+ mImmediatelyAfterVoiceInput = true;
+
+ InputConnection ic = mContext.getCurrentInputConnection();
+ if (!mContext.isFullscreenMode()) {
+ // Start listening for updates to the text from typing, etc.
+ if (ic != null) {
+ ExtractedTextRequest req = new ExtractedTextRequest();
+ ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR);
+ }
+ }
+ mContext.vibrate();
+
+ final List<CharSequence> nBest = new ArrayList<CharSequence>();
+ for (String c : mVoiceResults.candidates) {
+ if (capitalizeFirstWord) {
+ c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length());
+ }
+ nBest.add(c);
+ }
+ if (nBest.size() == 0) {
+ return;
+ }
+ String bestResult = nBest.get(0).toString();
+ mVoiceInput.logVoiceInputDelivered(bestResult.length());
+ mHints.registerVoiceResult(bestResult);
+
+ if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text
+ mContext.commitTyped(ic);
+ EditingUtil.appendText(ic, bestResult);
+ if (ic != null) ic.endBatchEdit();
+
+ mVoiceInputHighlighted = true;
+ mWordToSuggestions.putAll(mVoiceResults.alternatives);
+ onCancelVoice();
+ }
+
+ public void switchToRecognitionStatusView(final boolean configurationChanging) {
+ final boolean configChanged = configurationChanging;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mContext.setCandidatesViewShown(false);
+ mRecognizing = true;
+ View v = mVoiceInput.getView();
+ ViewParent p = v.getParent();
+ if (p != null && p instanceof ViewGroup) {
+ ((ViewGroup)p).removeView(v);
+ }
+ mContext.setInputView(v);
+ mContext.updateInputViewShown();
+ if (configChanged) {
+ mVoiceInput.onConfigurationChanged();
+ }
+ }});
+ }
+
+ private void switchToLastInputMethod() {
+ IBinder token = mContext.getWindow().getWindow().getAttributes().token;
+ mImm.switchToLastInputMethod(token);
+ }
+
+ private void reallyStartListening(boolean swipe, final boolean configurationChanging) {
+ if (!VOICE_INSTALLED) {
+ return;
+ }
+ if (!mHasUsedVoiceInput) {
+ // The user has started a voice input, so remember that in the
+ // future (so we don't show the warning dialog after the first run).
+ SharedPreferences.Editor editor =
+ PreferenceManager.getDefaultSharedPreferences(mContext).edit();
+ editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true);
+ SharedPreferencesCompat.apply(editor);
+ mHasUsedVoiceInput = true;
+ }
+
+ if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) {
+ // The user has started a voice input from an unsupported locale, so remember that
+ // in the future (so we don't show the warning dialog the next time they do this).
+ SharedPreferences.Editor editor =
+ PreferenceManager.getDefaultSharedPreferences(mContext).edit();
+ editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true);
+ SharedPreferencesCompat.apply(editor);
+ mHasUsedVoiceInputUnsupportedLocale = true;
+ }
+
+ // Clear N-best suggestions
+ mContext.clearSuggestions();
+
+ FieldContext context = makeFieldContext();
+ mVoiceInput.startListening(context, swipe);
+ switchToRecognitionStatusView(configurationChanging);
+ }
+
+ public void startListening(final boolean swipe, IBinder token,
+ final boolean configurationChanging) {
+ // TODO: remove swipe which is no longer used.
+ if (VOICE_INSTALLED) {
+ if (needsToShowWarningDialog()) {
+ // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel.
+ showVoiceWarningDialog(swipe, token, configurationChanging);
+ } else {
+ reallyStartListening(swipe, configurationChanging);
+ }
+ }
+ }
+
+
+ private boolean fieldCanDoVoice(FieldContext fieldContext) {
+ return !mPasswordText
+ && mVoiceInput != null
+ && !mVoiceInput.isBlacklistedField(fieldContext);
+ }
+
+ private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
+ return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext)
+ && !(attribute != null
+ && IME_OPTION_NO_MICROPHONE.equals(attribute.privateImeOptions))
+ && SpeechRecognizer.isRecognitionAvailable(mContext);
+ }
+
+ public void loadSettings(EditorInfo attribute, SharedPreferences sp) {
+ mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false);
+ mHasUsedVoiceInputUnsupportedLocale =
+ sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false);
+
+ mLocaleSupportedForVoiceInput = SubtypeSwitcher.getInstance().isVoiceSupported(
+ SubtypeSwitcher.getInstance().getInputLocaleStr());
+
+ if (VOICE_INSTALLED) {
+ final String voiceMode = sp.getString(PREF_VOICE_MODE,
+ mContext.getString(R.string.voice_mode_main));
+ mVoiceButtonEnabled = !voiceMode.equals(mContext.getString(R.string.voice_mode_off))
+ && shouldShowVoiceButton(makeFieldContext(), attribute);
+ mVoiceButtonOnPrimary = voiceMode.equals(mContext.getString(R.string.voice_mode_main));
+ }
+ }
+
+ public void destroy() {
+ if (VOICE_INSTALLED && mVoiceInput != null) {
+ mVoiceInput.destroy();
+ }
+ }
+
+ public void onStartInputView(IBinder token) {
+ // If IME is in voice mode, but still needs to show the voice warning dialog,
+ // keep showing the warning.
+ if (mSubtypeSwitcher.isVoiceMode() && needsToShowWarningDialog() && token != null) {
+ showVoiceWarningDialog(false, token, false);
+ }
+ }
+
+ public void onAttachedToWindow() {
+ // After onAttachedToWindow, we can show the voice warning dialog. See startListening()
+ // above.
+ mSubtypeSwitcher.setVoiceInput(mVoiceInput);
+ }
+
+ public void onConfigurationChanged(boolean configurationChanging) {
+ if (mRecognizing) {
+ switchToRecognitionStatusView(configurationChanging);
+ }
+ }
+
+ @Override
+ public void onCancelVoice() {
+ if (mRecognizing) {
+ if (mSubtypeSwitcher.isVoiceMode()) {
+ // If voice mode is being canceled within LatinIME (i.e. time-out or user
+ // cancellation etc.), onCancelVoice() will be called first. LatinIME thinks it's
+ // still in voice mode. LatinIME needs to call switchToLastInputMethod().
+ // Note that onCancelVoice() will be called again from SubtypeSwitcher.
+ switchToLastInputMethod();
+ } else if (mSubtypeSwitcher.isKeyboardMode()) {
+ // If voice mode is being canceled out of LatinIME (i.e. by user's IME switching or
+ // as a result of switchToLastInputMethod() etc.),
+ // onCurrentInputMethodSubtypeChanged() will be called first. LatinIME will know
+ // that it's in keyboard mode and SubtypeSwitcher will call onCancelVoice().
+ mRecognizing = false;
+ mContext.switchToKeyboardView();
+ }
+ }
+ }
+
+ @Override
+ public void onVoiceResults(List<String> candidates,
+ Map<String, List<CharSequence>> alternatives) {
+ if (!mRecognizing) {
+ return;
+ }
+ mVoiceResults.candidates = candidates;
+ mVoiceResults.alternatives = alternatives;
+ mHandler.updateVoiceResults();
+ }
+
+ public FieldContext makeFieldContext() {
+ SubtypeSwitcher switcher = SubtypeSwitcher.getInstance();
+ return new FieldContext(mContext.getCurrentInputConnection(),
+ mContext.getCurrentInputEditorInfo(), switcher.getInputLocaleStr(),
+ switcher.getEnabledLanguages());
+ }
+
+ private class VoiceResults {
+ List<String> candidates;
+ Map<String, List<CharSequence>> alternatives;
+ }
+}
diff --git a/java/src/com/android/inputmethod/voice/VoiceInput.java b/java/src/com/android/inputmethod/voice/VoiceInput.java
index f24c180d0..d51d8694d 100644
--- a/java/src/com/android/inputmethod/voice/VoiceInput.java
+++ b/java/src/com/android/inputmethod/voice/VoiceInput.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.voice;
+import com.android.inputmethod.latin.EditingUtil;
import com.android.inputmethod.latin.R;
import android.content.ContentResolver;
@@ -27,11 +28,12 @@ import android.os.Handler;
import android.os.Message;
import android.os.Parcelable;
import android.speech.RecognitionListener;
-import android.speech.SpeechRecognizer;
import android.speech.RecognizerIntent;
+import android.speech.SpeechRecognizer;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
+import android.view.inputmethod.InputConnection;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -84,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";
@@ -403,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:
@@ -423,8 +427,14 @@ public class VoiceInput implements OnClickListener {
mLogger.textModifiedByTypingDeletion(length);
}
- public void logTextModifiedByChooseSuggestion(int length) {
- mLogger.textModifiedByChooseSuggestion(length);
+ public void logTextModifiedByChooseSuggestion(String suggestion, int index,
+ String wordSeparators, InputConnection ic) {
+ EditingUtil.Range range = new EditingUtil.Range();
+ String wordToBeReplaced = EditingUtil.getWordAtCursor(ic, wordSeparators, range);
+ // If we enable phrase-based alternatives, only send up the first word
+ // in suggestion and wordToBeReplaced.
+ mLogger.textModifiedByChooseSuggestion(suggestion.length(), wordToBeReplaced.length(),
+ index, wordToBeReplaced, suggestion);
}
public void logKeyboardWarningDialogShown() {
@@ -455,10 +465,6 @@ public class VoiceInput implements OnClickListener {
mLogger.voiceInputDelivered(length);
}
- public void logNBestChoose(int index) {
- mLogger.nBestChoose(index);
- }
-
public void logInputEnded() {
mLogger.inputEnded();
}
@@ -552,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<String> results = resultsBundle
.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
@@ -634,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 188d1376e..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;
@@ -205,22 +206,22 @@ public class VoiceInputLogger {
mContext.sendBroadcast(i);
}
- public void textModifiedByChooseSuggestion(int length) {
+
+ public void textModifiedByChooseSuggestion(int suggestionLength, int replacedPhraseLength,
+ int index, String before, String after) {
setHasLoggingInfo(true);
Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED);
- i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length);
+ i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, suggestionLength);
+ i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_REPLACED_LENGTH, replacedPhraseLength);
i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE,
LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_CHOOSE_SUGGESTION);
- mContext.sendBroadcast(i);
- }
- public void nBestChoose(int index) {
- setHasLoggingInfo(true);
- Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.N_BEST_CHOOSE);
i.putExtra(LoggingEvents.VoiceIme.EXTRA_N_BEST_CHOOSE_INDEX, index);
+ i.putExtra(LoggingEvents.VoiceIme.EXTRA_BEFORE_N_BEST_CHOOSE, before);
+ i.putExtra(LoggingEvents.VoiceIme.EXTRA_AFTER_N_BEST_CHOOSE, after);
mContext.sendBroadcast(i);
}
-
+
public void inputEnded() {
setHasLoggingInfo(true);
mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.INPUT_ENDED));
diff --git a/java/src/com/android/inputmethod/voice/Whitelist.java b/java/src/com/android/inputmethod/voice/Whitelist.java
index 167b688ca..f4c24de0c 100644
--- a/java/src/com/android/inputmethod/voice/Whitelist.java
+++ b/java/src/com/android/inputmethod/voice/Whitelist.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.voice;
import android.os.Bundle;
+
import java.util.ArrayList;
import java.util.List;