diff options
Diffstat (limited to 'java/src')
17 files changed, 1262 insertions, 373 deletions
diff --git a/java/src/com/android/inputmethod/latin/BaseKeyboard.java b/java/src/com/android/inputmethod/latin/BaseKeyboard.java new file mode 100644 index 000000000..0327006c9 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/BaseKeyboard.java @@ -0,0 +1,883 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.util.Xml; +import android.view.InflateException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +/** + * 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> + * <Keyboard + * latin:keyWidth="%10p" + * latin:keyHeight="50px" + * latin:horizontalGap="2px" + * latin:verticalGap="2px" > + * <Row latin:keyWidth="32px" > + * <Key latin:keyLabel="A" /> + * ... + * </Row> + * ... + * </Keyboard> + * </pre> + */ +public class BaseKeyboard { + + static final String TAG = "BaseKeyboard"; + + // 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_INCLUDE = "include"; + private static final String TAG_MERGE = "merge"; + + public static final int EDGE_LEFT = 0x01; + public static final int EDGE_RIGHT = 0x02; + public static final int EDGE_TOP = 0x04; + public static final int EDGE_BOTTOM = 0x08; + + public static final int KEYCODE_SHIFT = -1; + public static final int KEYCODE_MODE_CHANGE = -2; + public static final int KEYCODE_CANCEL = -3; + public static final int KEYCODE_DONE = -4; + public static final int KEYCODE_DELETE = -5; + public static final int KEYCODE_ALT = -6; + + /** Horizontal gap default for all rows */ + private int mDefaultHorizontalGap; + + /** Default key width */ + private int mDefaultWidth; + + /** Default key height */ + private int mDefaultHeight; + + /** Default gap between rows */ + private int mDefaultVerticalGap; + + /** Is the keyboard in the shifted state */ + private boolean mShifted; + + /** List of shift keys in this keyboard */ + private final List<Key> mShiftKeys = new ArrayList<Key>(); + + /** 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; + + /** Keyboard mode, or zero, if none. */ + private final int mKeyboardMode; + + // Variables for pre-computing nearest keys. + + private static final int GRID_WIDTH = 10; + private static final int GRID_HEIGHT = 5; + private static final int GRID_SIZE = GRID_WIDTH * GRID_HEIGHT; + private int mCellWidth; + private int mCellHeight; + private int[][] mGridNeighbors; + private int mProximityThreshold; + /** Number of key widths from current touch point to search for nearest keys. */ + private static float SEARCH_DISTANCE = 1.8f; + + /** + * 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 static class Row { + /** Default width of a key in this row. */ + public int defaultWidth; + /** Default height of a key in this row. */ + public int defaultHeight; + /** Default horizontal gap between keys in this row. */ + public int defaultHorizontalGap; + /** Vertical gap following this row. */ + public int verticalGap; + /** + * Edge flags for this row of keys. Possible values that can be assigned are + * {@link Keyboard#EDGE_TOP EDGE_TOP} and {@link Keyboard#EDGE_BOTTOM EDGE_BOTTOM} + */ + public int rowEdgeFlags; + + /** The keyboard mode for this row */ + public int mode; + + private BaseKeyboard parent; + + public Row(BaseKeyboard parent) { + this.parent = parent; + } + + public Row(Resources res, BaseKeyboard parent, XmlResourceParser parser) { + this.parent = parent; + TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.BaseKeyboard); + defaultWidth = getDimensionOrFraction(a, + R.styleable.BaseKeyboard_keyWidth, + parent.mDisplayWidth, parent.mDefaultWidth); + defaultHeight = getDimensionOrFraction(a, + R.styleable.BaseKeyboard_keyHeight, + parent.mDisplayHeight, parent.mDefaultHeight); + defaultHorizontalGap = getDimensionOrFraction(a, + R.styleable.BaseKeyboard_horizontalGap, + parent.mDisplayWidth, parent.mDefaultHorizontalGap); + verticalGap = getDimensionOrFraction(a, + R.styleable.BaseKeyboard_verticalGap, + parent.mDisplayHeight, parent.mDefaultVerticalGap); + a.recycle(); + a = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.BaseKeyboard_Row); + rowEdgeFlags = a.getInt(R.styleable.BaseKeyboard_Row_rowEdgeFlags, 0); + mode = a.getResourceId(R.styleable.BaseKeyboard_Row_keyboardMode, 0); + } + } + + /** + * Class for describing the position and characteristics of a single key in the keyboard. + */ + public static class Key { + /** + * All the key codes (unicode or custom code) that this key could generate, zero'th + * being the most important. + */ + public int[] codes; + + /** Label to display */ + public CharSequence label; + /** Label to display when keyboard is in temporary shift mode */ + public CharSequence temporaryShiftLabel; + + /** Icon to display instead of a label. Icon takes precedence over a label */ + public Drawable icon; + /** Hint icon to display on the key in conjunction with the label */ + public Drawable hintIcon; + /** Preview version of the icon, for the preview popup */ + public Drawable iconPreview; + /** Width of the key, not including the gap */ + public int width; + /** Height of the key, not including the gap */ + public int height; + /** The horizontal gap before this key */ + public int gap; + /** Whether this key is sticky, i.e., a toggle key */ + public boolean sticky; + /** X coordinate of the key in the keyboard layout */ + public int x; + /** Y coordinate of the key in the keyboard layout */ + public int y; + /** The current pressed state of this key */ + public boolean pressed; + /** If this is a sticky key, is it on? */ + public boolean on; + /** Text to output when pressed. This can be multiple characters, like ".com" */ + public CharSequence text; + /** Popup characters */ + public CharSequence popupCharacters; + + /** + * Flags that specify the anchoring to edges of the keyboard for detecting touch events + * that are just out of the boundary of the key. This is a bit mask of + * {@link Keyboard#EDGE_LEFT}, {@link Keyboard#EDGE_RIGHT}, {@link Keyboard#EDGE_TOP} and + * {@link Keyboard#EDGE_BOTTOM}. + */ + public int edgeFlags; + /** Whether this is a modifier key, such as Shift or Alt */ + public boolean modifier; + /** The BaseKeyboard that this key belongs to */ + private BaseKeyboard keyboard; + /** + * If this key pops up a mini keyboard, this is the resource id for the XML layout for that + * keyboard. + */ + public int popupResId; + /** Whether this key repeats itself when held down */ + public boolean repeatable; + + + private final static int[] KEY_STATE_NORMAL_ON = { + android.R.attr.state_checkable, + android.R.attr.state_checked + }; + + private final static int[] KEY_STATE_PRESSED_ON = { + android.R.attr.state_pressed, + android.R.attr.state_checkable, + android.R.attr.state_checked + }; + + private final static int[] KEY_STATE_NORMAL_OFF = { + android.R.attr.state_checkable + }; + + private final static int[] KEY_STATE_PRESSED_OFF = { + android.R.attr.state_pressed, + android.R.attr.state_checkable + }; + + private final static int[] KEY_STATE_NORMAL = { + }; + + private final static int[] KEY_STATE_PRESSED = { + android.R.attr.state_pressed + }; + + /** Create an empty key with no attributes. */ + public Key(Row parent) { + keyboard = parent.parent; + height = parent.defaultHeight; + width = parent.defaultWidth; + gap = parent.defaultHorizontalGap; + edgeFlags = parent.rowEdgeFlags; + } + + /** Create a key with the given top-left coordinate and extract its attributes from + * the XML parser. + * @param res resources associated with the caller's context + * @param parent the row that this key belongs to. The row must already be attached to + * a {@link Keyboard}. + * @param x the x coordinate of the top-left + * @param y the y coordinate of the top-left + * @param parser the XML parser containing the attributes for this key + */ + public Key(Resources res, Row parent, int x, int y, XmlResourceParser parser) { + this(parent); + + this.x = x; + this.y = y; + + TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.BaseKeyboard); + + width = getDimensionOrFraction(a, R.styleable.BaseKeyboard_keyWidth, + keyboard.mDisplayWidth, parent.defaultWidth); + height = getDimensionOrFraction(a, R.styleable.BaseKeyboard_keyHeight, + keyboard.mDisplayHeight, parent.defaultHeight); + gap = getDimensionOrFraction(a, R.styleable.BaseKeyboard_horizontalGap, + keyboard.mDisplayWidth, parent.defaultHorizontalGap); + a.recycle(); + a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.BaseKeyboard_Key); + this.x += gap; + TypedValue codesValue = new TypedValue(); + a.getValue(R.styleable.BaseKeyboard_Key_codes, codesValue); + if (codesValue.type == TypedValue.TYPE_INT_DEC + || codesValue.type == TypedValue.TYPE_INT_HEX) { + codes = new int[] { codesValue.data }; + } else if (codesValue.type == TypedValue.TYPE_STRING) { + codes = parseCSV(codesValue.string.toString()); + } + + iconPreview = a.getDrawable(R.styleable.BaseKeyboard_Key_iconPreview); + setDefaultBounds(iconPreview); + popupCharacters = a.getText(R.styleable.BaseKeyboard_Key_popupCharacters); + popupResId = a.getResourceId(R.styleable.BaseKeyboard_Key_popupKeyboard, 0); + repeatable = a.getBoolean(R.styleable.BaseKeyboard_Key_isRepeatable, false); + modifier = a.getBoolean(R.styleable.BaseKeyboard_Key_isModifier, false); + sticky = a.getBoolean(R.styleable.BaseKeyboard_Key_isSticky, false); + edgeFlags = a.getInt(R.styleable.BaseKeyboard_Key_keyEdgeFlags, 0); + edgeFlags |= parent.rowEdgeFlags; + + icon = a.getDrawable(R.styleable.BaseKeyboard_Key_keyIcon); + setDefaultBounds(icon); + hintIcon = a.getDrawable(R.styleable.BaseKeyboard_Key_keyHintIcon); + setDefaultBounds(hintIcon); + + label = a.getText(R.styleable.BaseKeyboard_Key_keyLabel); + temporaryShiftLabel = a.getText(R.styleable.BaseKeyboard_Key_temporaryShiftKeyLabel); + text = a.getText(R.styleable.BaseKeyboard_Key_keyOutputText); + + if (codes == null && !TextUtils.isEmpty(label)) { + codes = new int[] { label.charAt(0) }; + } + a.recycle(); + } + + /** + * Informs the key that it has been pressed, in case it needs to change its appearance or + * state. + * @see #onReleased(boolean) + */ + public void onPressed() { + pressed = !pressed; + } + + /** + * Changes the pressed state of the key. If it is a sticky key, it will also change the + * toggled state of the key if the finger was release inside. + * @param inside whether the finger was released inside the key + * @see #onPressed() + */ + public void onReleased(boolean inside) { + pressed = !pressed; + if (sticky) { + on = !on; + } + } + + 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.e(TAG, "Error parsing keycodes " + value); + } + } + return values; + } + + /** + * Detects if a point falls inside this key. + * @param x the x-coordinate of the point + * @param y the y-coordinate of the point + * @return whether or not the point falls inside the key. If the key is attached to an edge, + * it will assume that all points between the key and the edge are considered to be inside + * the key. + */ + public boolean isInside(int x, int y) { + boolean leftEdge = (edgeFlags & EDGE_LEFT) > 0; + boolean rightEdge = (edgeFlags & EDGE_RIGHT) > 0; + boolean topEdge = (edgeFlags & EDGE_TOP) > 0; + boolean bottomEdge = (edgeFlags & EDGE_BOTTOM) > 0; + if ((x >= this.x || (leftEdge && x <= this.x + this.width)) + && (x < this.x + this.width || (rightEdge && x >= this.x)) + && (y >= this.y || (topEdge && y <= this.y + this.height)) + && (y < this.y + this.height || (bottomEdge && y >= this.y))) { + return true; + } else { + return false; + } + } + + /** + * Returns the square of the distance between the center 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 center of the key + */ + public int squaredDistanceFrom(int x, int y) { + int xDist = this.x + width / 2 - x; + int yDist = this.y + height / 2 - y; + return xDist * xDist + yDist * yDist; + } + + /** + * Returns the drawable state for the key, based on the current state and type of the key. + * @return the drawable state of the key. + * @see android.graphics.drawable.StateListDrawable#setState(int[]) + */ + public int[] getCurrentDrawableState() { + int[] states = KEY_STATE_NORMAL; + + if (on) { + if (pressed) { + states = KEY_STATE_PRESSED_ON; + } else { + states = KEY_STATE_NORMAL_ON; + } + } else { + if (sticky) { + if (pressed) { + states = KEY_STATE_PRESSED_OFF; + } else { + states = KEY_STATE_NORMAL_OFF; + } + } else { + if (pressed) { + states = KEY_STATE_PRESSED; + } + } + } + return states; + } + } + + /** + * Creates a keyboard from the given xml key layout file. + * @param context the application or service context + * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. + */ + public BaseKeyboard(Context context, int xmlLayoutResId) { + this(context, xmlLayoutResId, 0); + } + + /** + * Creates a keyboard from the given xml key layout file. Weeds out rows + * that have a keyboard mode defined but don't match the specified mode. + * @param context the application or service context + * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. + * @param modeId keyboard mode identifier + * @param width sets width of keyboard + * @param height sets height of keyboard + */ + public BaseKeyboard(Context context, int xmlLayoutResId, int modeId, int width, int height) { + mDisplayWidth = width; + mDisplayHeight = height; + + mDefaultHorizontalGap = 0; + mDefaultWidth = mDisplayWidth / 10; + mDefaultVerticalGap = 0; + mDefaultHeight = mDefaultWidth; + mKeyboardMode = modeId; + loadKeyboard(context, context.getResources().getXml(xmlLayoutResId)); + } + + /** + * Creates a keyboard from the given xml key layout file. Weeds out rows + * that have a keyboard mode defined but don't match the specified mode. + * @param context the application or service context + * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. + * @param modeId keyboard mode identifier + */ + public BaseKeyboard(Context context, int xmlLayoutResId, int modeId) { + DisplayMetrics dm = context.getResources().getDisplayMetrics(); + mDisplayWidth = dm.widthPixels; + mDisplayHeight = dm.heightPixels; + //Log.v(TAG, "keyboard's display metrics:" + dm); + + mDefaultHorizontalGap = 0; + mDefaultWidth = mDisplayWidth / 10; + mDefaultVerticalGap = 0; + mDefaultHeight = mDefaultWidth; + mKeyboardMode = modeId; + loadKeyboard(context, context.getResources().getXml(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 BaseKeyboard(Context context, int layoutTemplateResId, + CharSequence characters, int columns, int horizontalPadding) { + this(context, layoutTemplateResId); + int x = 0; + int y = 0; + int column = 0; + mTotalWidth = 0; + + Row row = new Row(this); + row.defaultHeight = mDefaultHeight; + row.defaultWidth = mDefaultWidth; + row.defaultHorizontalGap = mDefaultHorizontalGap; + row.verticalGap = mDefaultVerticalGap; + row.rowEdgeFlags = EDGE_TOP | EDGE_BOTTOM; + final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns; + for (int i = 0; i < characters.length(); i++) { + char c = characters.charAt(i); + if (column >= maxColumns + || x + mDefaultWidth + horizontalPadding > mDisplayWidth) { + x = 0; + y += mDefaultVerticalGap + mDefaultHeight; + column = 0; + } + final Key key = new Key(row); + key.x = x; + key.y = y; + key.label = String.valueOf(c); + key.codes = new int[] { c }; + column++; + x += key.width + key.gap; + mKeys.add(key); + if (x > mTotalWidth) { + mTotalWidth = x; + } + } + mTotalHeight = y + mDefaultHeight; + } + + public List<Key> getKeys() { + return mKeys; + } + + protected int getHorizontalGap() { + return mDefaultHorizontalGap; + } + + protected void setHorizontalGap(int gap) { + mDefaultHorizontalGap = gap; + } + + protected int getVerticalGap() { + return mDefaultVerticalGap; + } + + protected void setVerticalGap(int gap) { + mDefaultVerticalGap = gap; + } + + protected int getKeyHeight() { + return mDefaultHeight; + } + + protected void setKeyHeight(int height) { + mDefaultHeight = height; + } + + protected int getKeyWidth() { + return mDefaultWidth; + } + + protected void setKeyWidth(int width) { + mDefaultWidth = width; + } + + /** + * 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 boolean setShifted(boolean shiftState) { + for (final Key key : mShiftKeys) { + key.on = shiftState; + } + if (mShifted != shiftState) { + mShifted = shiftState; + return true; + } + return false; + } + + public boolean isShifted() { + return mShifted; + } + + public List<Key> getShiftKeys() { + return mShiftKeys; + } + + 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][]; + int[] indices = new int[mKeys.size()]; + final int gridWidth = GRID_WIDTH * mCellWidth; + final int gridHeight = GRID_HEIGHT * mCellHeight; + for (int x = 0; x < gridWidth; x += mCellWidth) { + for (int y = 0; y < gridHeight; y += mCellHeight) { + int count = 0; + for (int i = 0; i < mKeys.size(); i++) { + final Key key = mKeys.get(i); + if (key.squaredDistanceFrom(x, y) < mProximityThreshold || + key.squaredDistanceFrom(x + mCellWidth - 1, y) < mProximityThreshold || + key.squaredDistanceFrom(x + mCellWidth - 1, y + mCellHeight - 1) + < mProximityThreshold || + key.squaredDistanceFrom(x, y + mCellHeight - 1) < mProximityThreshold) { + indices[count++] = i; + } + } + int [] cell = new int[count]; + System.arraycopy(indices, 0, cell, 0, count); + mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell; + } + } + } + + /** + * Returns the indices of the keys that are closest to the given point. + * @param x the x-coordinate of the point + * @param y the y-coordinate of the point + * @return the array of integer indices for the nearest keys to the given point. If the given + * point is out of range, then an array of size zero is returned. + */ + public int[] getNearestKeys(int x, int y) { + if (mGridNeighbors == null) computeNearestNeighbors(); + if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) { + int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth); + if (index < GRID_SIZE) { + return mGridNeighbors[index]; + } + } + return new int[0]; + } + + // 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) { + return new Key(res, parent, x, y, parser); + } + + private static class KeyboardParseState { + private final int mKeyboardMode; + private int mCurrentX = 0; + private int mCurrentY = 0; + private int mMaxRowWidth = 0; + private int mTotalHeight = 0; + private Row mCurrentRow = null; + + public KeyboardParseState(int keyboardMode) { + mKeyboardMode = keyboardMode; + } + + public int getX() { + return mCurrentX; + } + + public int getY() { + return mCurrentY; + } + + public Row getRow() { + return mCurrentRow; + } + + // return true if the row is valid for this keyboard mode + public boolean startRow(Row row) { + mCurrentX = 0; + mCurrentRow = row; + return row.mode == 0 || row.mode == mKeyboardMode; + } + + public void skipRow() { + mCurrentRow = null; + } + + public void endRow() { + if (mCurrentRow == null) + throw new InflateException("orphant end row tag"); + mCurrentY += mCurrentRow.verticalGap + mCurrentRow.defaultHeight; + mCurrentRow = null; + } + + public void endKey(Key key) { + mCurrentX += key.gap + key.width; + if (mCurrentX > mMaxRowWidth) + mMaxRowWidth = mCurrentX; + } + + public void endKeyboard(int defaultVerticalGap) { + mTotalHeight = mCurrentY - defaultVerticalGap; + } + + public int getMaxRowWidth() { + return mMaxRowWidth; + } + + public int getTotalHeight() { + return mTotalHeight; + } + } + + private void loadKeyboard(Context context, XmlResourceParser parser) { + try { + KeyboardParseState state = new KeyboardParseState(mKeyboardMode); + parseKeyboard(context.getResources(), parser, state); + // mTotalWidth is the width of this keyboard which is maximum width of row. + mTotalWidth = state.getMaxRowWidth(); + mTotalHeight = state.getTotalHeight(); + } catch (XmlPullParserException e) { + throw new IllegalArgumentException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void parseKeyboard(Resources res, XmlResourceParser parser, KeyboardParseState state) + throws XmlPullParserException, IOException { + Key key = null; + + int event; + while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { + if (event == XmlResourceParser.START_TAG) { + String tag = parser.getName(); + if (TAG_ROW.equals(tag)) { + // TODO createRowFromXml should not be called from BaseKeyboard constructor. + Row row = createRowFromXml(res, parser); + if (!state.startRow(row)) + skipToEndOfRow(parser, state); + } else if (TAG_KEY.equals(tag)) { + // TODO createKeyFromXml should not be called from BaseKeyboard constructor. + key = createKeyFromXml(res, state.getRow(), state.getX(), state.getY(), + parser); + mKeys.add(key); + if (key.codes[0] == KEYCODE_SHIFT) + mShiftKeys.add(key); + } else if (TAG_KEYBOARD.equals(tag)) { + parseKeyboardAttributes(res, parser); + } else if (TAG_INCLUDE.equals(tag)) { + if (parser.getDepth() == 0) + throw new InflateException("<include /> cannot be the root element"); + parseInclude(res, parser, state); + } else if (TAG_MERGE.equals(tag)) { + throw new InflateException("<merge> must not be appeared in keyboard XML file"); + } else { + throw new InflateException("unknown start tag: " + tag); + } + } else if (event == XmlResourceParser.END_TAG) { + String tag = parser.getName(); + if (TAG_KEY.equals(tag)) { + state.endKey(key); + } else if (TAG_ROW.equals(tag)) { + state.endRow(); + } else if (TAG_KEYBOARD.equals(tag)) { + state.endKeyboard(mDefaultVerticalGap); + } else if (TAG_INCLUDE.equals(tag)) { + ; + } else if (TAG_MERGE.equals(tag)) { + return; + } else { + throw new InflateException("unknown end tag: " + tag); + } + } + } + } + + private void parseInclude(Resources res, XmlResourceParser parent, KeyboardParseState state) + throws XmlPullParserException, IOException { + final TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parent), + R.styleable.BaseKeyboard_Include); + final int keyboardLayout = a.getResourceId( + R.styleable.BaseKeyboard_Include_keyboardLayout, 0); + a.recycle(); + if (keyboardLayout == 0) + throw new InflateException("<include /> must have keyboardLayout attribute"); + final XmlResourceParser parser = res.getLayout(keyboardLayout); + + int event; + while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { + if (event == XmlResourceParser.START_TAG) { + String name = parser.getName(); + if (TAG_MERGE.equals(name)) { + parseKeyboard(res, parser, state); + return; + } else { + throw new InflateException( + "include keyboard layout must have <merge> root element"); + } + } + } + } + + private void skipToEndOfRow(XmlResourceParser parser, KeyboardParseState state) + throws XmlPullParserException, IOException { + int event; + while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { + if (event == XmlResourceParser.END_TAG) { + String tag = parser.getName(); + if (TAG_ROW.equals(tag)) { + state.skipRow(); + return; + } + } + } + throw new InflateException("can not find </Row>"); + } + + private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) { + TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.BaseKeyboard); + + mDefaultWidth = getDimensionOrFraction(a, + R.styleable.BaseKeyboard_keyWidth, + mDisplayWidth, mDisplayWidth / 10); + mDefaultHeight = getDimensionOrFraction(a, + R.styleable.BaseKeyboard_keyHeight, + mDisplayHeight, 50); + mDefaultHorizontalGap = getDimensionOrFraction(a, + R.styleable.BaseKeyboard_horizontalGap, + mDisplayWidth, 0); + mDefaultVerticalGap = getDimensionOrFraction(a, + R.styleable.BaseKeyboard_verticalGap, + mDisplayHeight, 0); + mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE); + mProximityThreshold = mProximityThreshold * mProximityThreshold; + a.recycle(); + } + + static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) { + 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; + } + + protected static void setDefaultBounds(Drawable drawable) { + if (drawable != null) + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight()); + } +} diff --git a/java/src/com/android/inputmethod/latin/KeyDetector.java b/java/src/com/android/inputmethod/latin/KeyDetector.java index 76fe1200e..3902b60a3 100644 --- a/java/src/com/android/inputmethod/latin/KeyDetector.java +++ b/java/src/com/android/inputmethod/latin/KeyDetector.java @@ -16,14 +16,13 @@ package com.android.inputmethod.latin; -import android.inputmethodservice.Keyboard; -import android.inputmethodservice.Keyboard.Key; +import com.android.inputmethod.latin.BaseKeyboard.Key; import java.util.Arrays; import java.util.List; abstract class KeyDetector { - protected Keyboard mKeyboard; + protected BaseKeyboard mKeyboard; private Key[] mKeys; @@ -35,7 +34,7 @@ abstract class KeyDetector { protected int mProximityThresholdSquare; - public Key[] setKeyboard(Keyboard keyboard, float correctionX, float correctionY) { + public Key[] setKeyboard(BaseKeyboard keyboard, float correctionX, float correctionY) { if (keyboard == null) throw new NullPointerException(); mCorrectionX = (int)correctionX; diff --git a/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java b/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java index a7b695eb3..86dbf1f60 100644 --- a/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java @@ -260,8 +260,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha mMode = mode; mImeOptions = imeOptions; if (enableVoice != mHasVoice) { - // TODO clean up this unnecessary recursive call. - setVoiceMode(enableVoice, mVoiceOnPrimary); + setVoiceMode(mHasVoice, mVoiceOnPrimary); } mIsSymbols = isSymbols; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index b6fee1170..687870622 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -34,7 +34,6 @@ 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; @@ -69,6 +68,7 @@ import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -95,8 +95,8 @@ public class LatinIME extends InputMethodService 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_AUTO_COMPLETION_THRESHOLD = "auto_completion_threshold"; + 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 @@ -192,8 +192,7 @@ 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; @@ -448,6 +447,7 @@ public class LatinIME extends InputMethodService int[] dictionaries = getDictionary(orig); mSuggest = new Suggest(this, dictionaries); + loadAndSetAutoCompletionThreshold(sp); updateAutoTextEnabled(saveLocale); if (mUserDictionary != null) mUserDictionary.close(); mUserDictionary = new UserDictionary(this, mInputLocale); @@ -1153,9 +1153,9 @@ public class LatinIME extends InputMethodService } } - private void showInputMethodPicker() { + private void showInputMethodSubtypePicker() { ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)) - .showInputMethodPicker(); + .showInputMethodSubtypePicker(); } private void onOptionKeyPressed() { @@ -1171,7 +1171,7 @@ public class LatinIME extends InputMethodService private void onOptionKeyLongPressed() { if (!isShowingOptionDialog()) { if (LatinIMEUtil.hasMultipleEnabledIMEs(this)) { - showInputMethodPicker(); + showInputMethodSubtypePicker(); } else { launchSettings(); } @@ -1186,29 +1186,29 @@ public class LatinIME extends InputMethodService public void onKey(int primaryCode, int[] keyCodes, int x, int y) { long when = SystemClock.uptimeMillis(); - if (primaryCode != Keyboard.KEYCODE_DELETE || + if (primaryCode != BaseKeyboard.KEYCODE_DELETE || when > mLastKeyTime + QUICK_PRESS) { mDeleteCount = 0; } mLastKeyTime = when; final boolean distinctMultiTouch = mKeyboardSwitcher.hasDistinctMultitouch(); switch (primaryCode) { - case Keyboard.KEYCODE_DELETE: + case BaseKeyboard.KEYCODE_DELETE: handleBackspace(); mDeleteCount++; LatinImeLogger.logOnDelete(); break; - case Keyboard.KEYCODE_SHIFT: + case BaseKeyboard.KEYCODE_SHIFT: // Shift key is handled in onPress() when device has distinct multi-touch panel. if (!distinctMultiTouch) handleShift(); break; - case Keyboard.KEYCODE_MODE_CHANGE: + case BaseKeyboard.KEYCODE_MODE_CHANGE: // Symbol key is handled in onPress() when device has distinct multi-touch panel. if (!distinctMultiTouch) changeKeyboardMode(); break; - case Keyboard.KEYCODE_CANCEL: + case BaseKeyboard.KEYCODE_CANCEL: if (!isShowingOptionDialog()) { handleClose(); } @@ -1864,13 +1864,13 @@ 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) { + if (mAfterVoiceInput && mShowingVoiceSuggestions) { mVoiceInput.flushAllTextModificationCounters(); // send this intent AFTER logging any prior aggregated edits. - mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.length()); + mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index, + mWordSeparators, + getCurrentInputConnection()); } final boolean correcting = TextEntryState.isCorrecting(); @@ -2285,10 +2285,10 @@ public class LatinIME extends InputMethodService vibrate(); playKeyClick(primaryCode); final boolean distinctMultiTouch = mKeyboardSwitcher.hasDistinctMultitouch(); - if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_SHIFT) { + if (distinctMultiTouch && primaryCode == BaseKeyboard.KEYCODE_SHIFT) { mShiftKeyState.onPress(); handleShift(); - } else if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_MODE_CHANGE) { + } else if (distinctMultiTouch && primaryCode == BaseKeyboard.KEYCODE_MODE_CHANGE) { mSymbolKeyState.onPress(); changeKeyboardMode(); } else { @@ -2302,11 +2302,11 @@ public class LatinIME extends InputMethodService ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).keyReleased(); //vibrate(); final boolean distinctMultiTouch = mKeyboardSwitcher.hasDistinctMultitouch(); - if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_SHIFT) { + if (distinctMultiTouch && primaryCode == BaseKeyboard.KEYCODE_SHIFT) { if (mShiftKeyState.isMomentary()) resetShift(); mShiftKeyState.onRelease(); - } else if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_MODE_CHANGE) { + } else if (distinctMultiTouch && primaryCode == BaseKeyboard.KEYCODE_MODE_CHANGE) { if (mSymbolKeyState.isMomentary()) changeKeyboardMode(); mSymbolKeyState.onRelease(); @@ -2329,8 +2329,8 @@ public class LatinIME extends InputMethodService private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) { return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) - && !(attribute != null - && IME_OPTION_NO_MICROPHONE.equals(attribute.privateImeOptions)) + && !(attribute != null && attribute.privateImeOptions != null + && attribute.privateImeOptions.equals(IME_OPTION_NO_MICROPHONE)) && SpeechRecognizer.isRecognitionAvailable(this); } @@ -2365,7 +2365,7 @@ 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 BaseKeyboard.KEYCODE_DELETE: sound = AudioManager.FX_KEYPRESS_DELETE; break; case KEYCODE_ENTER: @@ -2489,6 +2489,9 @@ public class LatinIME extends InputMethodService mLocaleSupportedForVoiceInput = voiceInputSupportedLocales.contains(mInputLocale); mShowSuggestions = sp.getBoolean(PREF_SHOW_SUGGESTIONS, true); + mAutoCorrectEnabled = mShowSuggestions && isAutoCorrectEnabled(sp); + mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(sp); + loadAndSetAutoCompletionThreshold(sp); if (VOICE_INSTALLED) { final String voiceMode = sp.getString(PREF_VOICE_MODE, @@ -2503,15 +2506,61 @@ public class LatinIME extends InputMethodService mEnableVoice = enableVoice; mVoiceOnPrimary = voiceOnPrimary; } - 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); } + /** + * 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]); + } + } 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)); + } + // 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() { mSuggestPuncList = new ArrayList<CharSequence>(); mSuggestPuncs = mResources.getString(R.string.suggested_punctuations); @@ -2544,8 +2593,7 @@ public class LatinIME extends InputMethodService launchSettings(); break; case POS_METHOD: - ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)) - .showInputMethodPicker(); + showInputMethodSubtypePicker(); break; } } diff --git a/java/src/com/android/inputmethod/latin/LatinIMESettings.java b/java/src/com/android/inputmethod/latin/LatinIMESettings.java index ffff33da2..99d8a622e 100644 --- a/java/src/com/android/inputmethod/latin/LatinIMESettings.java +++ b/java/src/com/android/inputmethod/latin/LatinIMESettings.java @@ -43,6 +43,9 @@ 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"; + private static final String PREF_SHOW_SUGGESTIONS = "show_suggestions"; + private static final String PREF_AUTO_COMPLETION_THRESHOLD = "auto_completion_threshold"; + private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion"; /* package */ static final String PREF_SETTINGS_KEY = "settings_key"; private static final String TAG = "LatinIMESettings"; @@ -53,6 +56,9 @@ public class LatinIMESettings extends PreferenceActivity private CheckBoxPreference mQuickFixes; private ListPreference mVoicePreference; private ListPreference mSettingsKeyPreference; + private CheckBoxPreference mShowSuggestions; + private ListPreference mAutoCompletionThreshold; + private CheckBoxPreference mBigramSuggestion; private boolean mVoiceOn; private VoiceInputLogger mLogger; @@ -60,6 +66,18 @@ public class LatinIMESettings extends PreferenceActivity private boolean mOkClicked = false; private String mVoiceModeOff; + private void ensureConsistencyOfAutoCompletionSettings() { + if (mShowSuggestions.isChecked()) { + mAutoCompletionThreshold.setEnabled(true); + final String autoCompletionOff = getResources().getString( + R.string.auto_completion_threshold_mode_value_off); + final String currentSetting = mAutoCompletionThreshold.getValue(); + mBigramSuggestion.setEnabled(!currentSetting.equals(autoCompletionOff)); + } else { + mAutoCompletionThreshold.setEnabled(false); + mBigramSuggestion.setEnabled(false); + } + } @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -73,6 +91,11 @@ 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); + + mShowSuggestions = (CheckBoxPreference) findPreference(PREF_SHOW_SUGGESTIONS); + mAutoCompletionThreshold = (ListPreference) findPreference(PREF_AUTO_COMPLETION_THRESHOLD); + mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS); + ensureConsistencyOfAutoCompletionSettings(); } @Override @@ -108,6 +131,7 @@ public class LatinIMESettings extends PreferenceActivity showVoiceConfirmation(); } } + ensureConsistencyOfAutoCompletionSettings(); mVoiceOn = !(prefs.getString(VOICE_SETTINGS_KEY, mVoiceModeOff).equals(mVoiceModeOff)); updateVoiceModeSummary(); updateSettingsKeySummary(); diff --git a/java/src/com/android/inputmethod/latin/LatinIMEUtil.java b/java/src/com/android/inputmethod/latin/LatinIMEUtil.java index 85ecaee50..d93639063 100644 --- a/java/src/com/android/inputmethod/latin/LatinIMEUtil.java +++ b/java/src/com/android/inputmethod/latin/LatinIMEUtil.java @@ -168,4 +168,58 @@ 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; + } } diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java index a8ab9cc98..dd7bc8ac1 100644 --- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java +++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java @@ -20,7 +20,6 @@ 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 { @@ -65,7 +64,7 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang public static void onAddSuggestedWord(String word, int typeId, DataType dataType) { } - public static void onSetKeyboard(Keyboard kb) { + public static void onSetKeyboard(BaseKeyboard kb) { } } diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboard.java b/java/src/com/android/inputmethod/latin/LatinKeyboard.java index e33ae145b..e10346570 100644 --- a/java/src/com/android/inputmethod/latin/LatinKeyboard.java +++ b/java/src/com/android/inputmethod/latin/LatinKeyboard.java @@ -30,16 +30,16 @@ 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.HashMap; import java.util.List; import java.util.Locale; -public class LatinKeyboard extends Keyboard { +public class LatinKeyboard extends BaseKeyboard { private static final boolean DEBUG_PREFERRED_LETTER = false; private static final String TAG = "LatinKeyboard"; @@ -48,7 +48,7 @@ public class LatinKeyboard extends Keyboard { private Drawable mShiftLockIcon; private Drawable mShiftLockPreviewIcon; - private Drawable mOldShiftIcon; + private final HashMap<Key, Drawable> mOldShiftIcons = new HashMap<Key, Drawable>(); private Drawable mSpaceIcon; private Drawable mSpaceAutoCompletionIndicator; private Drawable mSpacePreviewIcon; @@ -58,15 +58,10 @@ public class LatinKeyboard extends Keyboard { private Drawable m123MicPreviewIcon; private final Drawable mButtonArrowLeftIcon; private final Drawable mButtonArrowRightIcon; - private Key mShiftKey; private Key mEnterKey; private Key mF1Key; - private Drawable mF1HintIcon; 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; @@ -91,10 +86,6 @@ public class LatinKeyboard extends Keyboard { // 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; @@ -136,30 +127,12 @@ public class LatinKeyboard extends Keyboard { 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); - mF1HintIcon = res.getDrawable(R.drawable.hint_settings); 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 @@ -182,143 +155,105 @@ public class LatinKeyboard extends Keyboard { 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) { - // TODO should clean up this method + private static void resetKeyAttributes(Key key, CharSequence label) { + key.popupCharacters = null; + key.popupResId = 0; + key.text = null; + key.iconPreview = null; + key.icon = null; + key.label = label; + } + + public void setImeOptions(Resources res, int mode, int options) { 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)) { + 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); + resetKeyAttributes(mEnterKey, 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); + resetKeyAttributes(mEnterKey, 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); + resetKeyAttributes(mEnterKey, res.getText(R.string.label_done_key)); break; case EditorInfo.IME_ACTION_SEARCH: + resetKeyAttributes(mEnterKey, null); 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 = null; - 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; - } + resetKeyAttributes(mEnterKey, res.getText(R.string.label_send_key)); break; } // Set the initial size of the preview icon - if (mEnterKey.iconPreview != null) { - setDefaultBounds(mEnterKey.iconPreview); - } + setDefaultBounds(mEnterKey.iconPreview); } } - - void enableShiftLock() { - int index = getShiftKeyIndex(); - if (index >= 0) { - mShiftKey = getKeys().get(index); - if (mShiftKey instanceof LatinKey) { - ((LatinKey)mShiftKey).enableShiftLock(); + + public void enableShiftLock() { + for (final Key key : getShiftKeys()) { + if (key instanceof LatinKey) { + ((LatinKey)key).enableShiftLock(); } - mOldShiftIcon = mShiftKey.icon; + mOldShiftIcons.put(key, key.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; - } + public void setShiftLocked(boolean shiftLocked) { + for (final Key key : getShiftKeys()) { + key.on = shiftLocked; + key.icon = mShiftLockIcon; } + mShiftState = shiftLocked ? SHIFT_LOCKED : SHIFT_ON; } - boolean isShiftLocked() { + public boolean isShiftLocked() { return mShiftState == SHIFT_LOCKED; } - + @Override public boolean setShifted(boolean shiftState) { boolean shiftChanged = false; - if (mShiftKey != null) { + if (getShiftKeys().size() > 0) { + for (final Key key : getShiftKeys()) { + if (shiftState == false) { + key.on = false; + key.icon = mOldShiftIcons.get(key); + } else if (mShiftState == SHIFT_OFF) { + key.icon = mShiftLockIcon; + } + } 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 if (mShiftState == SHIFT_OFF) { + shiftChanged = mShiftState == SHIFT_OFF; + mShiftState = SHIFT_ON; } + return shiftChanged; } else { return super.setShifted(shiftState); } - return shiftChanged; } @Override public boolean isShifted() { - if (mShiftKey != null) { + if (getShiftKeys().size() > 0) { return mShiftState != SHIFT_OFF; } else { return super.isShifted(); } } + public boolean isTemporaryUpperCase() { + return mIsAlphaKeyboard && isShifted() && !isShiftLocked(); + } + /* package */ boolean isAlphaKeyboard() { return mIsAlphaKeyboard; } @@ -336,25 +271,20 @@ public class LatinKeyboard extends Keyboard { mMicIcon = mRes.getDrawable(R.drawable.sym_keyboard_mic); m123MicIcon = mRes.getDrawable(R.drawable.sym_keyboard_123_mic); } - updateDynamicKeys(); + updateF1Key(); 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(); + updateF1Key(); } - private void updateDynamicKeys() { - // Update KEYCODE_MODE_CHANGE key only on alphabet mode, not on symbol mode. + private void updateF1Key() { + if (mF1Key == null) return; if (m123Key != null && mIsAlphaKeyboard) { if (mVoiceEnabled && !mHasVoiceButton) { m123Key.icon = m123MicIcon; @@ -367,26 +297,18 @@ public class LatinKeyboard extends Keyboard { } } - // Update KEYCODE_F1 key. Please note that some keyboard layout has no F1 key. - if (mF1Key != null) { - if (mHasVoiceButton && mVoiceEnabled) { - mF1Key.codes = new int[] { LatinKeyboardView.KEYCODE_VOICE }; - mF1Key.label = null; - // HACK: draw mMicIcon and mF1HintIcon at the same time - mF1Key.icon = new BitmapDrawable(mRes, drawSynthesizedSettingsHintImage( - mF1Key.width, mF1Key.height + mVerticalGap, mMicIcon, mF1HintIcon)); - mF1Key.iconPreview = mMicPreviewIcon; - mF1Key.popupResId = R.xml.popup_mic; - } else { - mF1Key.label = ","; - mF1Key.codes = new int[] { ',' }; - // HACK: draw only mF1HintIcon on offscreen buffer to adjust position of '...' to - // the above synthesized icon - mF1Key.icon = new BitmapDrawable(mRes, drawSynthesizedSettingsHintImage( - mF1Key.width, mF1Key.height + mVerticalGap, null, mF1HintIcon)); - mF1Key.iconPreview = null; - mF1Key.popupResId = R.xml.popup_comma; - } + if (mHasVoiceButton && mVoiceEnabled) { + mF1Key.codes = new int[] { LatinKeyboardView.KEYCODE_VOICE }; + mF1Key.label = null; + mF1Key.icon = mMicIcon; + mF1Key.iconPreview = mMicPreviewIcon; + mF1Key.popupResId = R.xml.popup_mic; + } else { + mF1Key.label = ","; + mF1Key.codes = new int[] { ',' }; + mF1Key.icon = null; + mF1Key.iconPreview = null; + mF1Key.popupResId = R.xml.popup_comma; } } @@ -398,14 +320,6 @@ public class LatinKeyboard extends Keyboard { 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; } @@ -434,29 +348,6 @@ public class LatinKeyboard extends Keyboard { return bounds.width(); } - // Overlay two images. Note that mainIcon can be null. - private Bitmap drawSynthesizedSettingsHintImage( - int width, int height, Drawable mainIcon, Drawable hintIcon) { - if (hintIcon == null) - return null; - 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 centered position - if (mainIcon != null) { - setDefaultBounds(mainIcon); - final int drawableX = (width - mainIcon.getIntrinsicWidth()) / 2; - final int drawableY = (height - mainIcon.getIntrinsicHeight()) / 2; - 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, @@ -772,8 +663,7 @@ public class LatinKeyboard extends Keyboard { return textSize; } - // TODO LatinKey could be static class - class LatinKey extends Keyboard.Key { + class LatinKey extends BaseKeyboard.Key { // functional normal state (with properties) private final int[] KEY_STATE_FUNCTIONAL_NORMAL = { @@ -788,7 +678,7 @@ public class LatinKeyboard extends Keyboard { private boolean mShiftLockEnabled; - public LatinKey(Resources res, Keyboard.Row parent, int x, int y, + public LatinKey(Resources res, BaseKeyboard.Row parent, int x, int y, XmlResourceParser parser) { super(res, parent, x, y, parser); if (popupCharacters != null && popupCharacters.length() == 0) { @@ -821,8 +711,6 @@ public class LatinKeyboard extends Keyboard { */ @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; } @@ -842,15 +730,6 @@ public class LatinKeyboard extends Keyboard { } 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; - } } /** diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java index c1b1008b0..83b761904 100644 --- a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java +++ b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java @@ -16,6 +16,8 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.latin.BaseKeyboard.Key; + import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; @@ -29,8 +31,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; @@ -47,7 +47,6 @@ 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; @@ -159,7 +158,6 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx // 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; // XML attribute private int mKeyTextSize; @@ -178,10 +176,8 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx private int mPopupLayout; // Main keyboard - private Keyboard mKeyboard; + private BaseKeyboard mKeyboard; private Key[] mKeys; - // TODO this attribute should be gotten from Keyboard. - private int mKeyboardVerticalGap; // Key preview popup private TextView mPreviewText; @@ -244,11 +240,6 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx private final Paint mPaint; private final Rect mPadding; private final Rect mClipRegion = new Rect(0, 0, 0, 0); - // This map caches key label text height in pixel as value and key label text size as map key. - private final HashMap<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"; private final UIHandler mHandler = new UIHandler(); @@ -477,7 +468,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx mPreviewPopup = new PopupWindow(context); if (previewLayout != 0) { mPreviewText = (TextView) inflate.inflate(previewLayout, null); - mPreviewTextSizeLarge = (int) res.getDimension(R.dimen.key_preview_text_size_large); + mPreviewTextSizeLarge = (int) mPreviewText.getTextSize(); mPreviewPopup.setContentView(mPreviewText); mPreviewPopup.setBackgroundDrawable(null); } else { @@ -577,7 +568,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx * @see #getKeyboard() * @param keyboard the keyboard to display in this view */ - public void setKeyboard(Keyboard keyboard) { + public void setKeyboard(BaseKeyboard keyboard) { if (mKeyboard != null) { dismissKeyPreview(); } @@ -588,9 +579,8 @@ 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 @@ -605,7 +595,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx * @return the currently attached keyboard * @see #setKeyboard(Keyboard) */ - public Keyboard getKeyboard() { + public BaseKeyboard getKeyboard() { return mKeyboard; } @@ -725,7 +715,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx * the touch distance from a key's center to avoid taking a square root. * @param keyboard */ - private void computeProximityThreshold(Keyboard keyboard) { + private void computeProximityThreshold(BaseKeyboard keyboard) { if (keyboard == null) return; final Key[] keys = mKeys; if (keys == null) return; @@ -733,7 +723,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx int dimensionSum = 0; for (int i = 0; i < length; i++) { Key key = keys[i]; - dimensionSum += Math.min(key.width, key.height + mKeyboardVerticalGap) + key.gap; + dimensionSum += Math.min(key.width, key.height) + key.gap; } if (dimensionSum < 0 || length == 0) return; mKeyDetector.setProximityThreshold((int) (dimensionSum * 1.4f / length)); @@ -785,14 +775,13 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx 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) { - drawSingleKey = true; - } + // Is clipRegion completely contained within the invalidated key? + if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left && + invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top && + invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right && + invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) { + drawSingleKey = true; + } } canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR); final int keyCount = keys.length; @@ -808,72 +797,65 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx String label = key.label == null? null : adjustCase(key.label).toString(); final Rect bounds = keyBackground.getBounds(); - if (key.width != bounds.right || key.height != bounds.bottom) { + if (key.width != bounds.right || + key.height != bounds.bottom) { keyBackground.setBounds(0, 0, key.width, key.height); } canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop); keyBackground.draw(canvas); - boolean shouldDrawIcon = true; + boolean drawHintIcon = true; if (label != null) { + // If keyboard is multi-touch capable and in temporary upper case state and key has + // tempoarary shift label, label should be hint character and hint icon should not + // be drawn. + if (mHasDistinctMultitouch + && mKeyboard instanceof LatinKeyboard + && ((LatinKeyboard)mKeyboard).isTemporaryUpperCase() + && key.temporaryShiftLabel != null) { + label = key.temporaryShiftLabel.toString(); + drawHintIcon = false; + } + // 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.setTextSize(mLabelTextSize); paint.setTypeface(Typeface.DEFAULT_BOLD); } else { - labelSize = mKeyTextSize; + paint.setTextSize(mKeyTextSize); paint.setTypeface(mKeyTextStyle); } - paint.setTextSize(labelSize); - - Integer labelHeightValue = mTextHeightCache.get(labelSize); - final int labelHeight; - if (labelHeightValue != null) { - labelHeight = labelHeightValue; - } else { - Rect textBounds = new Rect(); - paint.getTextBounds(KEY_LABEL_HEIGHT_REFERENCE_CHAR, 0, 1, textBounds); - labelHeight = textBounds.height(); - mTextHeightCache.put(labelSize, labelHeight); - } - // Draw 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); + // Draw the text + canvas.drawText(label, + (key.width - padding.left - padding.right) / 2 + + padding.left, + (key.height - padding.top - padding.bottom) / 2 + + (paint.getTextSize() - paint.descent()) / 2 + padding.top, + 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. - shouldDrawIcon = isCommaKeyLabelOrNumberAtEdgeOfPopupChars(key); } - if (key.icon != null && shouldDrawIcon) { - // Special handing for the upper-right number hint icons - final int drawableWidth; - final int drawableHeight; - final int drawableX; - final int drawableY; - if (isNumberAtEdgeOfPopupChars(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 + padding.left; - drawableY = (key.height - padding.top - padding.bottom - drawableHeight) - / 2 + padding.top; - } + Drawable icon = null; + if (key.label == null && key.icon != null) + icon = key.icon; + if (icon == null && key.hintIcon != null && drawHintIcon) + icon = key.hintIcon; + if (icon != null) { + // Hack for key hint icon displaying at the top right corner of the key. + final int drawableWidth = icon == key.hintIcon + ? key.width : icon.getIntrinsicWidth(); + final int drawableHeight = icon == key.hintIcon + ? key.height : icon.getIntrinsicHeight(); + + final int drawableX = (key.width - padding.left - padding.right + - drawableWidth) / 2 + padding.left; + final int drawableY = (key.height - padding.top - padding.bottom + - drawableHeight) / 2 + padding.top; canvas.translate(drawableX, drawableY); - key.icon.setBounds(0, 0, drawableWidth, drawableHeight); - key.icon.draw(canvas); + icon.setBounds(0, 0, drawableWidth, drawableHeight); + icon.draw(canvas); canvas.translate(-drawableX, -drawableY); } canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop); @@ -938,16 +920,18 @@ 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) return; // Should not draw number hint icons - if (key.icon != null && !isCommaKeyLabelOrNumberAtEdgeOfPopupChars(key)) { + if (key.icon != null && key.label == null) { mPreviewText.setCompoundDrawables(null, null, null, key.iconPreview != null ? key.iconPreview : key.icon); mPreviewText.setText(null); } else { + // 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) { @@ -1029,14 +1013,13 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only * one key is changing it's content. Any changes that affect the position or size of the key * may not be honored. - * @param key key in the attached {@link Keyboard}. + * @param key key in the attached {@link BaseKeyboard}. * @see #invalidateAllKeys */ public void invalidateKey(Key key) { if (key == null) return; mInvalidatedKey = key; - // TODO we should clean up this and record key's region to use in onBufferDraw. mDirtyRect.union(key.x + getPaddingLeft(), key.y + getPaddingTop(), key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop()); onBufferDraw(); @@ -1107,12 +1090,12 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx // Override default ProximityKeyDetector. miniKeyboard.mKeyDetector = new MiniKeyboardKeyDetector(mMiniKeyboardSlideAllowance); - Keyboard keyboard; + BaseKeyboard keyboard; if (popupKey.popupCharacters != null) { - keyboard = new Keyboard(getContext(), popupKeyboardId, popupKey.popupCharacters, + keyboard = new BaseKeyboard(getContext(), popupKeyboardId, popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight()); } else { - keyboard = new Keyboard(getContext(), popupKeyboardId); + keyboard = new BaseKeyboard(getContext(), popupKeyboardId); } miniKeyboard.setKeyboard(keyboard); miniKeyboard.setPopupParent(this); @@ -1132,7 +1115,8 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx // 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 & BaseKeyboard.EDGE_TOP) != 0 + && (edgeFlags & BaseKeyboard.EDGE_BOTTOM) != 0; } /** @@ -1226,19 +1210,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx return false; } - private static boolean isCommaKeyLabelOrNumberAtEdgeOfPopupChars(Key key) { - return isNumberAtEdgeOfPopupChars(key) || isCommaKeyLabel(key); - } - - private static boolean isCommaKeyLabel(Key key) { - return ",".equals(key.label); - } - - private static boolean isNumberAtEdgeOfPopupChars(Key key) { - return isNumberAtLeftmostPopupChar(key) || isNumberAtRightmostPopupChar(key); - } - - /* package */ static boolean isNumberAtLeftmostPopupChar(Key key) { + private static boolean isNumberAtLeftmostPopupChar(Key key) { if (key.popupCharacters != null && key.popupCharacters.length() > 0 && isAsciiDigit(key.popupCharacters.charAt(0))) { return true; @@ -1246,14 +1218,6 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx return false; } - /* package */ static boolean isNumberAtRightmostPopupChar(Key key) { - if (key.popupCharacters != null && key.popupCharacters.length() > 0 - && isAsciiDigit(key.popupCharacters.charAt(key.popupCharacters.length() - 1))) { - return true; - } - return false; - } - private static boolean isAsciiDigit(char c) { return (c < 0x80) && Character.isDigit(c); } @@ -1273,7 +1237,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx 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/latin/LatinKeyboardView.java b/java/src/com/android/inputmethod/latin/LatinKeyboardView.java index 22d39f7aa..f3d045bec 100644 --- a/java/src/com/android/inputmethod/latin/LatinKeyboardView.java +++ b/java/src/com/android/inputmethod/latin/LatinKeyboardView.java @@ -16,11 +16,11 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.latin.BaseKeyboard.Key; + 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; @@ -39,7 +39,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { static final int KEYCODE_NEXT_LANGUAGE = -104; static final int KEYCODE_PREV_LANGUAGE = -105; - private Keyboard mPhoneKeyboard; + private BaseKeyboard mPhoneKeyboard; /** Whether we've started dropping move events because we found a big jump */ private boolean mDroppingEvents; @@ -61,7 +61,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { super(context, attrs, defStyle); } - public void setPhoneKeyboard(Keyboard phoneKeyboard) { + public void setPhoneKeyboard(BaseKeyboard phoneKeyboard) { mPhoneKeyboard = phoneKeyboard; } @@ -76,7 +76,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { } @Override - public void setKeyboard(Keyboard k) { + public void setKeyboard(BaseKeyboard k) { super.setKeyboard(k); // One-seventh of the keyboard width seems like a reasonable threshold mJumpThresholdSquare = k.getMinWidth() / 7; @@ -108,10 +108,10 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { @Override protected CharSequence adjustCase(CharSequence label) { - Keyboard keyboard = getKeyboard(); - if (keyboard.isShifted() - && keyboard instanceof LatinKeyboard + BaseKeyboard keyboard = getKeyboard(); + if (keyboard instanceof LatinKeyboard && ((LatinKeyboard) keyboard).isAlphaKeyboard() + && keyboard.isShifted() && !TextUtils.isEmpty(label) && label.length() < 3 && Character.isLowerCase(label.charAt(0))) { label = label.toString().toUpperCase(); @@ -120,7 +120,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { } public boolean setShiftLocked(boolean shiftLocked) { - Keyboard keyboard = getKeyboard(); + BaseKeyboard keyboard = getKeyboard(); if (keyboard instanceof LatinKeyboard) { ((LatinKeyboard)keyboard).setShiftLocked(shiftLocked); invalidateAllKeys(); @@ -257,7 +257,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { private int mLastY; private Paint mPaint; - private void setKeyboardLocal(Keyboard k) { + private void setKeyboardLocal(BaseKeyboard k) { if (DEBUG_AUTO_PLAY) { findKeys(); if (mHandler2 == null) { diff --git a/java/src/com/android/inputmethod/latin/MiniKeyboardKeyDetector.java b/java/src/com/android/inputmethod/latin/MiniKeyboardKeyDetector.java index 356e62d48..5f4c93734 100644 --- a/java/src/com/android/inputmethod/latin/MiniKeyboardKeyDetector.java +++ b/java/src/com/android/inputmethod/latin/MiniKeyboardKeyDetector.java @@ -16,7 +16,7 @@ package com.android.inputmethod.latin; -import android.inputmethodservice.Keyboard.Key; +import com.android.inputmethod.latin.BaseKeyboard.Key; class MiniKeyboardKeyDetector extends KeyDetector { private static final int MAX_NEARBY_KEYS = 1; diff --git a/java/src/com/android/inputmethod/latin/PointerTracker.java b/java/src/com/android/inputmethod/latin/PointerTracker.java index 448e27910..d1cdbfe26 100644 --- a/java/src/com/android/inputmethod/latin/PointerTracker.java +++ b/java/src/com/android/inputmethod/latin/PointerTracker.java @@ -16,12 +16,11 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.latin.BaseKeyboard.Key; import com.android.inputmethod.latin.LatinKeyboardBaseView.OnKeyboardActionListener; import com.android.inputmethod.latin.LatinKeyboardBaseView.UIHandler; import android.content.res.Resources; -import android.inputmethodservice.Keyboard; -import android.inputmethodservice.Keyboard.Key; import android.util.Log; import android.view.MotionEvent; @@ -45,7 +44,7 @@ public class PointerTracker { // 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[] KEY_DELETE = { BaseKeyboard.KEYCODE_DELETE }; private final UIProxy mProxy; private final UIHandler mHandler; @@ -53,6 +52,7 @@ public class PointerTracker { private OnKeyboardActionListener mListener; private final boolean mHasDistinctMultitouch; + private BaseKeyboard mKeyboard; private Key[] mKeys; private int mKeyHysteresisDistanceSquared = -1; @@ -183,9 +183,10 @@ public class PointerTracker { mListener = listener; } - public void setKeyboard(Key[] keys, float keyHysteresisDistance) { - if (keys == null || keyHysteresisDistance < 0) + public void setKeyboard(BaseKeyboard keyboard, Key[] keys, float keyHysteresisDistance) { + if (keyboard == null || keys == null || keyHysteresisDistance < 0) throw new IllegalArgumentException(); + mKeyboard = keyboard; mKeys = keys; mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance); // Update current key index because keyboard layout has been changed. @@ -205,8 +206,8 @@ public class PointerTracker { if (key == null) return false; int primaryCode = key.codes[0]; - return primaryCode == Keyboard.KEYCODE_SHIFT - || primaryCode == Keyboard.KEYCODE_MODE_CHANGE; + return primaryCode == BaseKeyboard.KEYCODE_SHIFT + || primaryCode == BaseKeyboard.KEYCODE_MODE_CHANGE; } public boolean isModifier() { @@ -284,7 +285,7 @@ public class PointerTracker { mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this); mIsRepeatableKey = true; } - mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this); + startLongPressTimer(keyIndex); } showKeyPreviewAndUpdateKey(keyIndex); } @@ -296,14 +297,15 @@ public class PointerTracker { return; KeyState keyState = mKeyState; int keyIndex = keyState.onMoveKey(x, y); - if (isValidKeyIndex(keyIndex)) { + Key key = getKey(keyIndex); + if (key != null) { if (keyState.getKeyIndex() == NOT_A_KEY) { keyState.onMoveToNewKey(keyIndex, x, y); - mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this); + startLongPressTimer(keyIndex); } else if (!isMinorMoveBounce(x, y, keyIndex)) { resetMultiTap(); keyState.onMoveToNewKey(keyIndex, x, y); - mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this); + startLongPressTimer(keyIndex); } } else { if (keyState.getKeyIndex() != NOT_A_KEY) { @@ -419,6 +421,20 @@ public class PointerTracker { } } + private void startLongPressTimer(int keyIndex) { + Key key = getKey(keyIndex); + // If keyboard is in temporary upper case state and the key has temporary shift label, + // long press should not be started. + if (isTemporaryUpperCase() && key.temporaryShiftLabel != null) + return; + mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this); + } + + private boolean isTemporaryUpperCase() { + return mKeyboard instanceof LatinKeyboard + && ((LatinKeyboard)mKeyboard).isTemporaryUpperCase(); + } + private void detectAndSendKey(int index, int x, int y, long eventTime) { final OnKeyboardActionListener listener = mListener; final Key key = getKey(index); @@ -440,12 +456,20 @@ public class PointerTracker { // Multi-tap if (mInMultiTap) { if (mTapCount != -1) { - mListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE, x, y); + mListener.onKey(BaseKeyboard.KEYCODE_DELETE, KEY_DELETE, x, y); } else { mTapCount = 0; } code = key.codes[mTapCount]; } + + // If keyboard is in temporary upper case state and key has temporary shift label, + // alternate character code should be sent. + if (isTemporaryUpperCase() && key.temporaryShiftLabel != null) { + code = key.temporaryShiftLabel.charAt(0); + 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 diff --git a/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java b/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java index d17bedb56..a6ff8cf8c 100644 --- a/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java +++ b/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java @@ -16,7 +16,7 @@ package com.android.inputmethod.latin; -import android.inputmethodservice.Keyboard.Key; +import com.android.inputmethod.latin.BaseKeyboard.Key; import java.util.Arrays; diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 3b898941f..01782339f 100755 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -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) { diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java index 9011191f1..1d7659ca3 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.latin.BaseKeyboard.Key; + import android.content.Context; -import android.inputmethodservice.Keyboard.Key; import android.text.format.DateFormat; import android.util.Log; diff --git a/java/src/com/android/inputmethod/voice/VoiceInput.java b/java/src/com/android/inputmethod/voice/VoiceInput.java index f24c180d0..4c54dd3c5 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; @@ -30,6 +31,7 @@ import android.speech.RecognitionListener; import android.speech.SpeechRecognizer; import android.speech.RecognizerIntent; import android.util.Log; +import android.view.inputmethod.InputConnection; import android.view.View; import android.view.View.OnClickListener; @@ -423,8 +425,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 +463,6 @@ public class VoiceInput implements OnClickListener { mLogger.voiceInputDelivered(length); } - public void logNBestChoose(int index) { - mLogger.nBestChoose(index); - } - public void logInputEnded() { mLogger.inputEnded(); } diff --git a/java/src/com/android/inputmethod/voice/VoiceInputLogger.java b/java/src/com/android/inputmethod/voice/VoiceInputLogger.java index 188d1376e..ec0ae649a 100644 --- a/java/src/com/android/inputmethod/voice/VoiceInputLogger.java +++ b/java/src/com/android/inputmethod/voice/VoiceInputLogger.java @@ -205,22 +205,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)); |